From 028a0dcd0e3e9611aa8c8c876bdc0cecf82f9aec Mon Sep 17 00:00:00 2001 From: Leo Kirchner Date: Fri, 18 Aug 2023 11:55:17 -0400 Subject: [PATCH 01/10] Upgrade to pydantic v2 --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index ed09e16..8fac988 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -17,7 +17,7 @@ include = [ [tool.poetry.dependencies] python = ">=3.8,<4.0" -pydantic = "^1.7.4,!=1.8,!=1.8.1" +pydantic = "^2.0.0" structlog = ">= 20.1.0, < 23.0.0" packaging = ">= 21.3, < 24.0" colorama = {version = "^0.4.3", optional = true} From f7275c1d7a7f254bff9a806c4b81465c2f6c24ae Mon Sep 17 00:00:00 2001 From: Leo Kirchner Date: Fri, 18 Aug 2023 11:57:26 -0400 Subject: [PATCH 02/10] Run pydantic-bump --- diffsync/__init__.py | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-) diff --git a/diffsync/__init__.py b/diffsync/__init__.py index 5669b25..17f6927 100644 --- a/diffsync/__init__.py +++ b/diffsync/__init__.py @@ -18,7 +18,7 @@ from inspect import isclass from typing import Callable, ClassVar, Dict, List, Optional, Tuple, Type, Union, Any, Set -from pydantic import BaseModel, PrivateAttr +from pydantic import ConfigDict, BaseModel, PrivateAttr import structlog # type: ignore from diffsync.diff import Diff @@ -104,12 +104,7 @@ class DiffSyncModel(BaseModel): _status_message: str = PrivateAttr("") """Message, if any, associated with the create/update/delete status value.""" - - class Config: # pylint: disable=too-few-public-methods - """Pydantic class configuration.""" - - # Let us have a DiffSync as an instance variable even though DiffSync is not a Pydantic model itself. - arbitrary_types_allowed = True + model_config = ConfigDict(arbitrary_types_allowed=True) def __init_subclass__(cls) -> None: """Validate that the various class attribute declarations correspond to actual instance fields. From 4596499259fd89e2093eafba2243ca07559d2e0f Mon Sep 17 00:00:00 2001 From: Leo Kirchner Date: Fri, 18 Aug 2023 13:22:48 -0400 Subject: [PATCH 03/10] Fix compatibility with pydantic v2 --- diffsync/__init__.py | 18 +++++++++--------- examples/01-multiple-data-sources/models.py | 6 +++--- examples/03-remote-system/models.py | 2 +- examples/04-get-update-instantiate/models.py | 6 +++--- examples/05-nautobot-peeringdb/models.py | 12 ++++++------ examples/06-ip-prefixes/models.py | 6 +++--- tests/unit/conftest.py | 4 ++-- tests/unit/test_diffsync_model.py | 8 ++++---- 8 files changed, 31 insertions(+), 31 deletions(-) diff --git a/diffsync/__init__.py b/diffsync/__init__.py index 17f6927..2bf08d9 100644 --- a/diffsync/__init__.py +++ b/diffsync/__init__.py @@ -106,24 +106,24 @@ class DiffSyncModel(BaseModel): """Message, if any, associated with the create/update/delete status value.""" model_config = ConfigDict(arbitrary_types_allowed=True) - def __init_subclass__(cls) -> None: + @classmethod + def __pydantic_init_subclass__(cls, **kwargs: Any) -> None: """Validate that the various class attribute declarations correspond to actual instance fields. Called automatically on subclass declaration. """ - variables = cls.__fields__.keys() # Make sure that any field referenced by name actually exists on the model for attr in cls._identifiers: - if attr not in variables and not hasattr(cls, attr): + if attr not in cls.model_fields and not hasattr(cls, attr): raise AttributeError(f"_identifiers {cls._identifiers} references missing or un-annotated attr {attr}") for attr in cls._shortname: - if attr not in variables: + if attr not in cls.model_fields: raise AttributeError(f"_shortname {cls._shortname} references missing or un-annotated attr {attr}") for attr in cls._attributes: - if attr not in variables: + if attr not in cls.model_fields: raise AttributeError(f"_attributes {cls._attributes} references missing or un-annotated attr {attr}") for attr in cls._children.values(): - if attr not in variables: + if attr not in cls.model_fields: raise AttributeError(f"_children {cls._children} references missing or un-annotated attr {attr}") # Any given field can only be in one of (_identifiers, _attributes, _children) @@ -147,7 +147,7 @@ def dict(self, **kwargs: Any) -> Dict: """Convert this DiffSyncModel to a dict, excluding the diffsync field by default as it is not serializable.""" if "exclude" not in kwargs: kwargs["exclude"] = {"diffsync"} - return super().dict(**kwargs) + return super().model_dump(**kwargs) def json(self, **kwargs: Any) -> StrType: """Convert this DiffSyncModel to a JSON string, excluding the diffsync field by default as it is not serializable.""" @@ -155,7 +155,7 @@ def json(self, **kwargs: Any) -> StrType: kwargs["exclude"] = {"diffsync"} if "exclude_defaults" not in kwargs: kwargs["exclude_defaults"] = True - return super().json(**kwargs) + return super().model_dump_json(**kwargs) def str(self, include_children: bool = True, indent: int = 0) -> StrType: """Build a detailed string representation of this DiffSyncModel and optionally its children.""" @@ -855,4 +855,4 @@ def count(self, model: Union[StrType, "DiffSyncModel", Type["DiffSyncModel"], No # DiffSyncModel references DiffSync and DiffSync references DiffSyncModel. Break the typing loop: -DiffSyncModel.update_forward_refs() +DiffSyncModel.model_rebuild() diff --git a/examples/01-multiple-data-sources/models.py b/examples/01-multiple-data-sources/models.py index 2908515..625415a 100644 --- a/examples/01-multiple-data-sources/models.py +++ b/examples/01-multiple-data-sources/models.py @@ -40,8 +40,8 @@ class Device(DiffSyncModel): _children = {"interface": "interfaces"} name: str - site_name: Optional[str] # note that this attribute is NOT included in _attributes - role: Optional[str] # note that this attribute is NOT included in _attributes + site_name: Optional[str] = None # note that this attribute is NOT included in _attributes + role: Optional[str] = None # note that this attribute is NOT included in _attributes interfaces: List = [] @@ -56,4 +56,4 @@ class Interface(DiffSyncModel): name: str device_name: str - description: Optional[str] + description: Optional[str] = None diff --git a/examples/03-remote-system/models.py b/examples/03-remote-system/models.py index 6a1f85c..00fff0e 100644 --- a/examples/03-remote-system/models.py +++ b/examples/03-remote-system/models.py @@ -32,4 +32,4 @@ class Country(DiffSyncModel): slug: str name: str region: str - population: Optional[int] + population: Optional[int] = 0 diff --git a/examples/04-get-update-instantiate/models.py b/examples/04-get-update-instantiate/models.py index 9a10599..b0f785d 100644 --- a/examples/04-get-update-instantiate/models.py +++ b/examples/04-get-update-instantiate/models.py @@ -38,8 +38,8 @@ class Device(DiffSyncModel): _children = {"interface": "interfaces", "site": "sites"} name: str - site_name: Optional[str] # note that this attribute is NOT included in _attributes - role: Optional[str] # note that this attribute is NOT included in _attributes + site_name: Optional[str] = None # note that this attribute is NOT included in _attributes + role: Optional[str] = None # note that this attribute is NOT included in _attributes interfaces: List = [] sites: List = [] @@ -55,4 +55,4 @@ class Interface(DiffSyncModel): name: str device_name: str - description: Optional[str] + description: Optional[str] = None diff --git a/examples/05-nautobot-peeringdb/models.py b/examples/05-nautobot-peeringdb/models.py index 2fecb04..46c5807 100644 --- a/examples/05-nautobot-peeringdb/models.py +++ b/examples/05-nautobot-peeringdb/models.py @@ -21,8 +21,8 @@ class RegionModel(DiffSyncModel): # Data type declarations for all identifiers and attributes name: str slug: str - description: Optional[str] - parent_name: Optional[str] # may be None + description: Optional[str] = None + parent_name: Optional[str] = None sites: List = [] # Not in _attributes or _identifiers, hence not included in diff calculations @@ -49,10 +49,10 @@ class SiteModel(DiffSyncModel): name: str slug: str status_slug: str - region_name: Optional[str] # may be None - description: Optional[str] - latitude: Optional[float] - longitude: Optional[float] + region_name: Optional[str] = None + description: Optional[str] = None + latitude: Optional[float] = None + longitude: Optional[float] = None # Not in _attributes or _identifiers, hence not included in diff calculations pk: Optional[Union[UUID, int]] diff --git a/examples/06-ip-prefixes/models.py b/examples/06-ip-prefixes/models.py index 2fc4abd..cede29d 100644 --- a/examples/06-ip-prefixes/models.py +++ b/examples/06-ip-prefixes/models.py @@ -11,6 +11,6 @@ class Prefix(DiffSyncModel): _attributes = ("vrf", "vlan_id", "tenant") prefix: str - vrf: Optional[str] - vlan_id: Optional[int] - tenant: Optional[str] + vrf: Optional[str] = None + vlan_id: Optional[int] = None + tenant: Optional[str] = None diff --git a/tests/unit/conftest.py b/tests/unit/conftest.py index e0dd85c..365ce4a 100644 --- a/tests/unit/conftest.py +++ b/tests/unit/conftest.py @@ -115,7 +115,7 @@ class Device(DiffSyncModel): _children = {"interface": "interfaces"} name: str - site_name: Optional[str] # note this is not included in _attributes + site_name: Optional[str] = None # note this is not included in _attributes role: str interfaces: List = [] @@ -143,7 +143,7 @@ class Interface(DiffSyncModel): name: str interface_type: str = "ethernet" - description: Optional[str] + description: Optional[str] = None @pytest.fixture diff --git a/tests/unit/test_diffsync_model.py b/tests/unit/test_diffsync_model.py index 90ecbc8..e7d6a6b 100644 --- a/tests/unit/test_diffsync_model.py +++ b/tests/unit/test_diffsync_model.py @@ -107,7 +107,7 @@ def test_diffsync_model_dict_with_data(make_interface): def test_diffsync_model_json_with_data(make_interface): intf = make_interface() # json() omits default values for brevity - assert intf.json() == '{"device_name": "device1", "name": "eth0"}' + assert intf.json() == '{"device_name":"device1","name":"eth0"}' def test_diffsync_model_str_with_data(make_interface): @@ -182,7 +182,7 @@ def test_diffsync_model_json_with_children(generic_diffsync, make_site, make_dev generic_diffsync.add(site1) generic_diffsync.add(device1) - assert site1.json() == '{"name": "site1", "devices": ["device1"]}' + assert site1.json() == '{"name":"site1","devices":["device1"]}' def test_diffsync_model_str_with_children(generic_diffsync, make_site, make_device, make_interface): @@ -294,7 +294,7 @@ class BadAttributes(DiffSyncModel): name: str # Note that short_name doesn't have a type annotation - making sure this works too - short_name = "short_name" + short_name: str = "short_name" assert "_attributes" in str(excinfo.value) assert "my_attr" in str(excinfo.value) @@ -310,7 +310,7 @@ class BadChildren(DiffSyncModel): _children = {"device": "devices"} name: str - short_name = "short_name" + short_name: str = "short_name" my_attr: int = 0 assert "_children" in str(excinfo.value) From 8732b2453ded43c4d101d6d7d7b6a4f73e1c7b3b Mon Sep 17 00:00:00 2001 From: Leo Kirchner Date: Fri, 18 Aug 2023 13:49:45 -0400 Subject: [PATCH 04/10] Bootstrap 2.0 migration docs for pydantic 2.0 change --- CHANGELOG.md | 44 ++++++++++++-------- docs/source/getting_started/index.rst | 6 +-- docs/source/index.rst | 1 + docs/source/upgrading/01-upgrading-to-2.0.md | 31 ++++++++++++++ docs/source/upgrading/index.rst | 5 +++ 5 files changed, 67 insertions(+), 20 deletions(-) create mode 100644 docs/source/upgrading/01-upgrading-to-2.0.md create mode 100644 docs/source/upgrading/index.rst diff --git a/CHANGELOG.md b/CHANGELOG.md index ed4de59..8433699 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,16 @@ # Changelog -## v1.10.0 - 2023-11-16 +All notable changes to this project will be documented in this file. + +The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/). + +## [2.0.0] + +### Changed + +- **BREAKING CHANGE** #236/240 - Upgrade to Pydantic v2. + +## [1.10.0] - 2023-11-16 ### Fixed @@ -11,7 +21,7 @@ - #247 - Deprecates Python 3.7 -## v1.9.0 - 2023-10-16 +## [1.9.0] - 2023-10-16 ### Added @@ -21,7 +31,7 @@ - #219 - Type hinting overhaul -## v1.8.0 - 2023-04-18 +## [1.8.0] - 2023-04-18 ### Added @@ -34,7 +44,7 @@ - #77/#188 - `sync_from()` and `sync_to()` now return the `Diff` that was applied. - #211 - Loosened `packaging` and `structlog` library dependency constraints for broader compatibility. -## v1.7.0 - 2022-11-03 +## [1.7.0] - 2022-11-03 ### Changed @@ -52,15 +62,15 @@ ### Fixed -- #149 Limit redundant CI concurrency +- #149 - Limit redundant CI concurrency -## v1.6.0 - 2022-07-09 +## [1.6.0] - 2022-07-09 ### Changed - #120 - Dropped support for Python 3.6, new minimum is Python 3.7 -## v1.5.1 - 2022-06-30 +## [1.5.1] - 2022-06-30 ### Added @@ -75,13 +85,13 @@ - #115 - Fixed ReadTheDocs rendering pipeline - #118 - Fixed a regression in `DiffSync.get(modelname, identifiers)` introduced in 1.5.0 -## v1.5.0 - 2022-06-07 +## [1.5.0] - 2022-06-07 ### Added - #106 - Add a new, optional, backend store based in Redis -## v1.4.3 - 2022-03-03 +## [1.4.3] - 2022-03-03 ### Fixed @@ -91,9 +101,9 @@ ### Changed -- #103 Update development dependencies +- #103 - Update development dependencies -## v1.4.2 - 2022-02-28 +## [1.4.2] - 2022-02-28 **WARNING** - #90 inadvertently introduced a breaking API change in DiffSync 1.4.0 through 1.4.2 (#101); this change was reverted in #102 for DiffSync 1.4.3 and later. We recommend not using this release, and moving to 1.4.3 instead. @@ -101,7 +111,7 @@ - #100 - Added explicit dependency on `packaging`. -## v1.4.1 - 2022-01-26 +## [1.4.1] - 2022-01-26 **WARNING** - #90 inadvertently introduced a breaking API change in DiffSync 1.4.0 through 1.4.2 (#101); this change was reverted in #102 for DiffSync 1.4.3 and later. We recommend not using this release, and moving to 1.4.3 instead. @@ -109,7 +119,7 @@ - #95 - Removed optional dependencies on `sphinx`, `m2r2`, `sphinx-rtd-theme`, `toml`. -## v1.4.0 - 2022-01-24 +## [1.4.0] - 2022-01-24 **WARNING** - #90 inadvertently introduced a breaking API change in DiffSync 1.4.0 through 1.4.2 (#101); this change was reverted in #102 for DiffSync 1.4.3 and later. We recommend not using this release, and moving to 1.4.3 instead. @@ -138,19 +148,19 @@ - #51 - Update minimum Pydantic version due to security advisory GHSA-5jqp-qgf6-3pvh - #63 - Fix type in Readme -## v1.3.0 - 2021-04-07 +## [1.3.0] - 2021-04-07 ### Added - #48 - added optional `callback` argument to `diff_from`/`diff_to`/`sync_from`/`sync_to` for use with progress reporting. -## v1.2.0 - 2020-12-08 +## [1.2.0] - 2020-12-08 ### Added - #45 - minimum Python version lowered from 3.7 to 3.6, also now tested against Python 3.9. -## v1.1.0 - 2020-12-01 +## [1.1.0] - 2020-12-01 ### Added @@ -168,6 +178,6 @@ - #44 - On CRUD failure, do not generate an extraneous "success" log message in addition to the "failed" message -## v1.0.0 - 2020-10-23 +## [1.0.0] - 2020-10-23 Initial release diff --git a/docs/source/getting_started/index.rst b/docs/source/getting_started/index.rst index 2b0e096..9f461ee 100644 --- a/docs/source/getting_started/index.rst +++ b/docs/source/getting_started/index.rst @@ -1,5 +1,5 @@ -############### -Getting Started -############### +######### +Upgrading +######### .. mdinclude:: 01-getting-started.md diff --git a/docs/source/index.rst b/docs/source/index.rst index 3b1a744..2c851f5 100644 --- a/docs/source/index.rst +++ b/docs/source/index.rst @@ -9,6 +9,7 @@ Welcome to DiffSync's documentation! getting_started/index core_engine/index examples/index + upgrading/index api/diffsync license/index diff --git a/docs/source/upgrading/01-upgrading-to-2.0.md b/docs/source/upgrading/01-upgrading-to-2.0.md new file mode 100644 index 0000000..10a7b08 --- /dev/null +++ b/docs/source/upgrading/01-upgrading-to-2.0.md @@ -0,0 +1,31 @@ +# Upgrading to 2.0 + +With diffsync 2.0, there a couple of breaking changes. What they are and how to deal with them is described in this document. + +## Upgrade to Pydantic's major version 2 + +A [migration guide](https://docs.pydantic.dev/latest/migration/) is available in the Pydantic documentation. Here are the key things that may apply to your usage of diffsync: + +- Any fields that are of type `Optional` now need to provide an explicit `None` default (you can use [bump-pydantic](https://github.com/pydantic/bump-pydantic) to deal with this automatically for the most part) + +```python +from typing import Optional + +from diffsync import DiffSyncModel + +# Before +class Person(DiffSyncModel): + _identifiers = ("name",) + _attributes = ("age",) + + name: str + age: Optional[int] + +# After +class BetterPerson(DiffSyncModel) + _identifiers = ("name",) + _attributes = ("age",) + + name: str + age: Optional[int] = None +``` \ No newline at end of file diff --git a/docs/source/upgrading/index.rst b/docs/source/upgrading/index.rst new file mode 100644 index 0000000..30b6e1e --- /dev/null +++ b/docs/source/upgrading/index.rst @@ -0,0 +1,5 @@ +######### +Upgrading +######### + +.. mdinclude:: 01-upgrading-to-2.0.md From ecbb1ca37befb3a11a7011b669f3940e428ceb34 Mon Sep 17 00:00:00 2001 From: Leo Kirchner Date: Thu, 24 Aug 2023 16:29:03 -0600 Subject: [PATCH 05/10] renames diffsync.DiffSync to diffsync.Adapter --- README.md | 2 +- diffsync/__init__.py | 21 ++++++++------ diffsync/helpers.py | 10 +++---- diffsync/store/__init__.py | 4 +-- docs/source/core_engine/01-flags.md | 4 +-- docs/source/core_engine/03-store.md | 28 +++++++++++-------- .../getting_started/01-getting-started.md | 16 +++++------ .../01-multiple-data-sources/backend_a.py | 4 +-- .../01-multiple-data-sources/backend_b.py | 4 +-- .../01-multiple-data-sources/backend_c.py | 4 +-- examples/02-callback-function/main.py | 10 +++---- examples/03-remote-system/local_adapter.py | 4 +-- examples/03-remote-system/nautobot_adapter.py | 4 +-- examples/03-remote-system/nautobot_models.py | 4 +-- examples/04-get-update-instantiate/README.md | 6 ++-- .../04-get-update-instantiate/backends.py | 6 ++-- .../05-nautobot-peeringdb/adapter_nautobot.py | 4 +-- .../adapter_peeringdb.py | 4 +-- examples/06-ip-prefixes/adapter_ipam_a.py | 4 +-- examples/06-ip-prefixes/adapter_ipam_b.py | 4 +-- tests/unit/conftest.py | 10 +++---- tests/unit/test_diffsync.py | 8 +++--- tests/unit/test_diffsync_model_flags.py | 4 +-- 23 files changed, 88 insertions(+), 81 deletions(-) diff --git a/README.md b/README.md index 96484f4..61eec47 100644 --- a/README.md +++ b/README.md @@ -15,7 +15,7 @@ DiffSync is at its most useful when you have multiple sources or sets of data to # Overview of DiffSync -DiffSync acts as an intermediate translation layer between all of the data sets you are diffing and/or syncing. In practical terms, this means that to use DiffSync, you will define a set of data models as well as the “adapters” needed to translate between each base data source and the data model. In Python terms, the adapters will be subclasses of the `DiffSync` class, and each data model class will be a subclass of the `DiffSyncModel` class. +DiffSync acts as an intermediate translation layer between all of the data sets you are diffing and/or syncing. In practical terms, this means that to use DiffSync, you will define a set of data models as well as the “adapters” needed to translate between each base data source and the data model. In Python terms, the adapters will be subclasses of the `Adapter` class, and each data model class will be a subclass of the `DiffSyncModel` class. ![Diffsync Components](https://raw.githubusercontent.com/networktocode/diffsync/develop/docs/images/diffsync_components.png "Diffsync Components") diff --git a/diffsync/__init__.py b/diffsync/__init__.py index 2bf08d9..39e7b2b 100644 --- a/diffsync/__init__.py +++ b/diffsync/__init__.py @@ -96,7 +96,7 @@ class DiffSyncModel(BaseModel): Can be set as a class attribute or an instance attribute as needed. """ - diffsync: Optional["DiffSync"] = None + diffsync: Optional["Adapter"] = None """Optional: the DiffSync instance that owns this model instance.""" _status: DiffSyncStatus = PrivateAttr(DiffSyncStatus.SUCCESS) @@ -183,7 +183,7 @@ def set_status(self, status: DiffSyncStatus, message: StrType = "") -> None: self._status_message = message @classmethod - def create_base(cls, diffsync: "DiffSync", ids: Dict, attrs: Dict) -> Optional[Self]: + def create_base(cls, diffsync: "Adapter", ids: Dict, attrs: Dict) -> Optional[Self]: """Instantiate this class, along with any platform-specific data creation. This method is not meant to be subclassed, users should redefine create() instead. @@ -201,7 +201,7 @@ def create_base(cls, diffsync: "DiffSync", ids: Dict, attrs: Dict) -> Optional[S return model @classmethod - def create(cls, diffsync: "DiffSync", ids: Dict, attrs: Dict) -> Optional[Self]: + def create(cls, diffsync: "Adapter", ids: Dict, attrs: Dict) -> Optional[Self]: """Instantiate this class, along with any platform-specific data creation. Subclasses must call `super().create()` or `self.create_base()`; they may wish to then override the default status information @@ -402,7 +402,7 @@ def remove_child(self, child: "DiffSyncModel") -> None: childs.remove(child.get_unique_id()) -class DiffSync: # pylint: disable=too-many-public-methods +class Adapter: # pylint: disable=too-many-public-methods """Class for storing a group of DiffSyncModel instances and diffing/synchronizing to another DiffSync instance.""" # In any subclass, you would add mapping of names to specific model classes here: @@ -535,7 +535,7 @@ def load_from_dict(self, data: Dict) -> None: def sync_from( # pylint: disable=too-many-arguments self, - source: "DiffSync", + source: "Adapter", diff_class: Type[Diff] = Diff, flags: DiffSyncFlags = DiffSyncFlags.NONE, callback: Optional[Callable[[StrType, int, int], None]] = None, @@ -573,7 +573,7 @@ def sync_from( # pylint: disable=too-many-arguments def sync_to( # pylint: disable=too-many-arguments self, - target: "DiffSync", + target: "Adapter", diff_class: Type[Diff] = Diff, flags: DiffSyncFlags = DiffSyncFlags.NONE, callback: Optional[Callable[[StrType, int, int], None]] = None, @@ -597,7 +597,7 @@ def sync_to( # pylint: disable=too-many-arguments def sync_complete( self, - source: "DiffSync", + source: "Adapter", diff: Diff, flags: DiffSyncFlags = DiffSyncFlags.NONE, logger: Optional[structlog.BoundLogger] = None, @@ -623,7 +623,7 @@ def sync_complete( def diff_from( self, - source: "DiffSync", + source: "Adapter", diff_class: Type[Diff] = Diff, flags: DiffSyncFlags = DiffSyncFlags.NONE, callback: Optional[Callable[[StrType, int, int], None]] = None, @@ -644,7 +644,7 @@ def diff_from( def diff_to( self, - target: "DiffSync", + target: "Adapter", diff_class: Type[Diff] = Diff, flags: DiffSyncFlags = DiffSyncFlags.NONE, callback: Optional[Callable[[StrType, int, int], None]] = None, @@ -854,5 +854,8 @@ def count(self, model: Union[StrType, "DiffSyncModel", Type["DiffSyncModel"], No return self.store.count(model=model) +# For backwards-compatibility, keep around the old name +DiffSync = Adapter + # DiffSyncModel references DiffSync and DiffSync references DiffSyncModel. Break the typing loop: DiffSyncModel.model_rebuild() diff --git a/diffsync/helpers.py b/diffsync/helpers.py index c4e64c5..6ed7bae 100644 --- a/diffsync/helpers.py +++ b/diffsync/helpers.py @@ -26,7 +26,7 @@ if TYPE_CHECKING: # pragma: no cover # For type annotation purposes, we have a circular import loop between __init__.py and this file. - from . import DiffSync, DiffSyncModel # pylint: disable=cyclic-import + from . import Adapter, DiffSyncModel # pylint: disable=cyclic-import class DiffSyncDiffer: # pylint: disable=too-many-instance-attributes @@ -37,8 +37,8 @@ class DiffSyncDiffer: # pylint: disable=too-many-instance-attributes def __init__( # pylint: disable=too-many-arguments self, - src_diffsync: "DiffSync", - dst_diffsync: "DiffSync", + src_diffsync: "Adapter", + dst_diffsync: "Adapter", flags: DiffSyncFlags, diff_class: Type[Diff] = Diff, callback: Optional[Callable[[str, int, int], None]] = None, @@ -288,8 +288,8 @@ class DiffSyncSyncer: # pylint: disable=too-many-instance-attributes def __init__( # pylint: disable=too-many-arguments self, diff: Diff, - src_diffsync: "DiffSync", - dst_diffsync: "DiffSync", + src_diffsync: "Adapter", + dst_diffsync: "Adapter", flags: DiffSyncFlags, callback: Optional[Callable[[str, int, int], None]] = None, ): diff --git a/diffsync/store/__init__.py b/diffsync/store/__init__.py index 77f1a9f..3dacf33 100644 --- a/diffsync/store/__init__.py +++ b/diffsync/store/__init__.py @@ -6,7 +6,7 @@ if TYPE_CHECKING: from diffsync import DiffSyncModel - from diffsync import DiffSync + from diffsync import Adapter class BaseStore: @@ -15,7 +15,7 @@ class BaseStore: def __init__( self, # pylint: disable=unused-argument *args: Any, # pylint: disable=unused-argument - diffsync: Optional["DiffSync"] = None, + diffsync: Optional["Adapter"] = None, name: str = "", **kwargs: Any, # pylint: disable=unused-argument ) -> None: diff --git a/docs/source/core_engine/01-flags.md b/docs/source/core_engine/01-flags.md index 542703e..12f4ca9 100644 --- a/docs/source/core_engine/01-flags.md +++ b/docs/source/core_engine/01-flags.md @@ -34,12 +34,12 @@ diff = nautobot.diff_from(local, flags=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 import Adapter from diffsync.enum import DiffSyncModelFlags from model import MyDeviceModel -class MyAdapter(DiffSync): +class MyAdapter(Adapter): device = MyDeviceModel def load(self, data): diff --git a/docs/source/core_engine/03-store.md b/docs/source/core_engine/03-store.md index 7a4a7bd..a3cb690 100644 --- a/docs/source/core_engine/03-store.md +++ b/docs/source/core_engine/03-store.md @@ -2,32 +2,36 @@ By default, `Diffsync` supports a local memory storage. All the loaded models from the adapters will be stored in memory, and become available for the diff calculation and sync process. This default behavior works well when executing all the steps in the same process, having access to the same memory space. However, if you want to scale out the execution of the tasks, running it in different processes or in totally different workers, a more distributed memory support is necessary. -The `store` is a class attribute in the `DiffSync` class, but all the store operations in that class are abstracted in the following methods: `get_all_model_names`, `get`, `get_by_uids`, `add`, `update`, `remove`, `get_or_instantiate`, `update_or_instantiate` and `count`. +The `store` is a class attribute in the `Adapter` class, but all the store operations in that class are abstracted in the following methods: `get_all_model_names`, `get`, `get_by_uids`, `add`, `update`, `remove`, `get_or_instantiate`, `update_or_instantiate` and `count`. ## Use the `LocalStore` Backend When you initialize the `Diffsync` Adapter class, there is an optional keyed-argument, `internal_storage_engine`, defaulting to the `LocalStore` class. ```python ->>> from diffsync import DiffSync ->>> adapter = DiffSync() ->>> type(adapter.store) - +>> > from diffsync import Adapter +>> > adapter = Adapter() +>> > type(adapter.store) +< + +class 'diffsync.store.local.LocalStore'> ``` ## Use the `RedisStore` Backend To get it, you have to install diffsync package with the "redis" extra option: `pip install diffsync[redis]` -The `RedisStore` backend, as the name suggests, connects to an external Redis service, to store data loaded by the `DiffSync` tasks. The biggest change is that it requires to initialize the Redis store class, before using it in the `DiffSync` adapter class. +The `RedisStore` backend, as the name suggests, connects to an external Redis service, to store data loaded by the `Adapter` tasks. The biggest change is that it requires to initialize the Redis store class, before using it in the `Adapter` adapter class. ```python ->>> from diffsync import DiffSync ->>> from diffsync.store.redis import RedisStore ->>> store = RedisStore(host="redis host") ->>> adapter = DiffSync(internal_storage_engine=store) ->>> type(adapter.store) - +>> > from diffsync import Adapter +>> > from diffsync.store.redis import RedisStore +>> > store = RedisStore(host="redis host") +>> > adapter = Adapter(internal_storage_engine=store) +>> > type(adapter.store) +< + +class 'diffsync.store.local.RedisStore'> ``` Notice that the `RedisStore` will validate, when initialized, that there is a reachability to the Redis host, and if not, will raise an exception: diff --git a/docs/source/getting_started/01-getting-started.md b/docs/source/getting_started/01-getting-started.md index 6f0cc0f..9b99cc3 100644 --- a/docs/source/getting_started/01-getting-started.md +++ b/docs/source/getting_started/01-getting-started.md @@ -1,5 +1,5 @@ To be able to properly compare different datasets, DiffSync relies on a shared data model that both systems must use. -Specifically, each system or dataset must provide a `DiffSync` "adapter" subclass, which in turn represents its dataset as instances of one or more `DiffSyncModel` data model classes. +Specifically, each system or dataset must provide a `Adapter` "adapter" subclass, which in turn represents its dataset as instances of one or more `DiffSyncModel` data model classes. When comparing two systems, DiffSync detects the intersection between the two systems (which data models they have in common, and which attributes are shared between each pair of data models) and uses this intersection to compare and/or synchronize the data. @@ -41,24 +41,24 @@ Currently the relationships between models are very loose by design. Instead of # Define your system adapter with DiffSync -A `DiffSync` "adapter" subclass must reference each model available at the top of the object by its modelname and must have a `top_level` attribute defined to indicate how the diff and the synchronization should be done. In the example below, `"site"` is the only top level object so the synchronization engine will only check all known `Site` instances and all children of each Site. In this case, as shown in the code above, `Device`s are children of `Site`s, so this is exactly the intended logic. +A `Adapter` "adapter" subclass must reference each model available at the top of the object by its modelname and must have a `top_level` attribute defined to indicate how the diff and the synchronization should be done. In the example below, `"site"` is the only top level object so the synchronization engine will only check all known `Site` instances and all children of each Site. In this case, as shown in the code above, `Device`s are children of `Site`s, so this is exactly the intended logic. ```python -from diffsync import DiffSync +from diffsync import Adapter -class BackendA(DiffSync): +class BackendA(Adapter): site = Site device = Device top_level = ["site"] ``` -It's up to the implementer to populate the `DiffSync`'s internal cache with the appropriate data. In the example below we are using the `load()` method to populate the cache but it's not mandatory, it could be done differently. +It's up to the implementer to populate the `Adapter`'s internal cache with the appropriate data. In the example below we are using the `load()` method to populate the cache but it's not mandatory, it could be done differently. ## Model Processing Ordering Logic -The models will be processed in a specfic order as defined by `top_level` atttribute on the `DiffSync` object and then the `_children` attribute on the `DiffSyncModel`. The processing algorithm is technically a "Preorder Tree Traversal", which means that "a parent node is processed before any of its child nodes is done." This can be described as: +The models will be processed in a specfic order as defined by `top_level` atttribute on the `Adapter` object and then the `_children` attribute on the `DiffSyncModel`. The processing algorithm is technically a "Preorder Tree Traversal", which means that "a parent node is processed before any of its child nodes is done." This can be described as: - Start with the first element of the first model in `top_level` and process it. - If that model has `_children` set on it, for each child of each child model, in order: @@ -145,7 +145,7 @@ NetworkImporterAdapter >>> ``` -# Store data in a `DiffSync` object +# Store data in a `Adapter` object To add a site to the local cache/store, you need to pass a valid `DiffSyncModel` object to the `add()` function. @@ -174,7 +174,7 @@ convenient to manage individual records (as in a database) or modify the entire ## Manage individual records To update individual records in a remote system, you need to extend your `DiffSyncModel` class(es) to define your own `create`, `update` and/or `delete` methods for each model. -A `DiffSyncModel` instance stores a reference to its parent `DiffSync` adapter instance in case you need to use it to look up other model instances from the `DiffSync`'s cache. +A `DiffSyncModel` instance stores a reference to its parent `Adapter` adapter instance in case you need to use it to look up other model instances from the `Adapter`'s cache. ```python class Device(DiffSyncModel): diff --git a/examples/01-multiple-data-sources/backend_a.py b/examples/01-multiple-data-sources/backend_a.py index a92435b..6edd799 100644 --- a/examples/01-multiple-data-sources/backend_a.py +++ b/examples/01-multiple-data-sources/backend_a.py @@ -16,7 +16,7 @@ """ # pylint: disable=wrong-import-order -from diffsync import DiffSync +from diffsync import Adapter from models import Site, Device, Interface # pylint: disable=no-name-in-module DATA = { @@ -31,7 +31,7 @@ } -class BackendA(DiffSync): +class BackendA(Adapter): """Example of a DiffSync adapter implementation.""" site = Site diff --git a/examples/01-multiple-data-sources/backend_b.py b/examples/01-multiple-data-sources/backend_b.py index 6fc7c20..98db921 100644 --- a/examples/01-multiple-data-sources/backend_b.py +++ b/examples/01-multiple-data-sources/backend_b.py @@ -16,7 +16,7 @@ """ # pylint: disable=wrong-import-order -from diffsync import DiffSync +from diffsync import Adapter from models import Site, Device, Interface # pylint: disable=no-name-in-module DATA = { @@ -35,7 +35,7 @@ } -class BackendB(DiffSync): +class BackendB(Adapter): """Example of a DiffSync adapter implementation.""" site = Site diff --git a/examples/01-multiple-data-sources/backend_c.py b/examples/01-multiple-data-sources/backend_c.py index a964e96..88ed91a 100644 --- a/examples/01-multiple-data-sources/backend_c.py +++ b/examples/01-multiple-data-sources/backend_c.py @@ -16,7 +16,7 @@ """ # pylint: disable=wrong-import-order -from diffsync import DiffSync +from diffsync import Adapter from models import Site, Device, Interface # pylint: disable=no-name-in-module DATA = { @@ -31,7 +31,7 @@ } -class BackendC(DiffSync): +class BackendC(Adapter): """Example of a DiffSync adapter implementation.""" site = Site diff --git a/examples/02-callback-function/main.py b/examples/02-callback-function/main.py index 514f2cb..84884f2 100755 --- a/examples/02-callback-function/main.py +++ b/examples/02-callback-function/main.py @@ -17,7 +17,7 @@ """ import random -from diffsync import DiffSync, DiffSyncModel +from diffsync import Adapter, DiffSyncModel from diffsync.logging import enable_console_logging @@ -30,7 +30,7 @@ class Number(DiffSyncModel): number: int -class DiffSync1(DiffSync): +class Adapter1(Adapter): """DiffSync adapter that contains a number of Numbers constructed in order.""" number = Number @@ -43,7 +43,7 @@ def load(self, count): # pylint: disable=arguments-differ self.add(Number(number=(i + 1))) -class DiffSync2(DiffSync): +class Adapter2(Adapter): """DiffSync adapter that contains a number of Numbers spread randomly across a range.""" number = Number @@ -69,11 +69,11 @@ def main(): enable_console_logging(verbosity=0) # Show WARNING and ERROR logs only # Create a DiffSync1 instance and load it with records numbered 1-100 - ds1 = DiffSync1() + ds1 = Adapter1() ds1.load(count=100) # Create a DiffSync2 instance and load it with 100 random records in the range 1-200 - ds2 = DiffSync2() + ds2 = Adapter2() ds2.load(count=100) # Identify and attempt to resolve the differences between the two, diff --git a/examples/03-remote-system/local_adapter.py b/examples/03-remote-system/local_adapter.py index eb39fdc..9f38d1e 100644 --- a/examples/03-remote-system/local_adapter.py +++ b/examples/03-remote-system/local_adapter.py @@ -4,13 +4,13 @@ from slugify import slugify # pylint: disable=import-error from models import Region, Country # pylint: disable=no-name-in-module -from diffsync import DiffSync +from diffsync import Adapter COUNTRIES_FILE = "countries.json" -class LocalAdapter(DiffSync): +class LocalAdapter(Adapter): """DiffSync Adapter to Load the list of regions and countries from a local JSON file.""" # Define all data models that this adapter makes use of. diff --git a/examples/03-remote-system/nautobot_adapter.py b/examples/03-remote-system/nautobot_adapter.py index e0fb386..9ec17c6 100644 --- a/examples/03-remote-system/nautobot_adapter.py +++ b/examples/03-remote-system/nautobot_adapter.py @@ -4,7 +4,7 @@ from nautobot_models import NautobotCountry, NautobotRegion -from diffsync import DiffSync +from diffsync import Adapter # pylint: disable=attribute-defined-outside-init @@ -22,7 +22,7 @@ ] -class NautobotAdapter(DiffSync): +class NautobotAdapter(Adapter): """Example of a DiffSync adapter implementation using pynautobot to communicate with a remote Nautobot system.""" # We are using NautobotCountry and NautobotRegion instead of Region and Country diff --git a/examples/03-remote-system/nautobot_models.py b/examples/03-remote-system/nautobot_models.py index c1377d8..2baba77 100644 --- a/examples/03-remote-system/nautobot_models.py +++ b/examples/03-remote-system/nautobot_models.py @@ -3,7 +3,7 @@ from models import Region, Country # pylint: disable=no-name-in-module -from diffsync import DiffSync +from diffsync import Adapter # pylint: disable=no-member,too-few-public-methods @@ -30,7 +30,7 @@ class NautobotCountry(Country): """Store the nautobot uuid in the object to allow update and delete of existing object.""" @classmethod - def create(cls, diffsync: DiffSync, ids: dict, attrs: dict): + def create(cls, diffsync: Adapter, ids: dict, attrs: dict): """Create a country object in Nautobot. Args: diff --git a/examples/04-get-update-instantiate/README.md b/examples/04-get-update-instantiate/README.md index ecb1390..14e2745 100644 --- a/examples/04-get-update-instantiate/README.md +++ b/examples/04-get-update-instantiate/README.md @@ -1,12 +1,12 @@ # Example 4 - Using get or update helpers -This example aims to expand on [Example 1](https://github.com/networktocode/diffsync/tree/main/examples/01-multiple-data-sources/README.md) that will take advantage of two new helper methods on the `DiffSync` class; `get_or_instantiate` and `update_or_instantiate`. +This example aims to expand on [Example 1](https://github.com/networktocode/diffsync/tree/main/examples/01-multiple-data-sources/README.md) that will take advantage of two new helper methods on the `Adapter` class; `get_or_instantiate` and `update_or_instantiate`. Both methods act similar to Django's `get_or_create` function to return the object and then a boolean to identify whether the object was created or not. Let's dive into each of them. ## get_or_instantiate -The following arguments are supported: model (`DiffSyncModel`), ids (dictionary), and attrs (dictionary). The `model` and `ids` are used to find an existing object. If the object does not currently exist within the `DiffSync` adapter, it will then use `model`, `ids`, and `attrs` to add the object. +The following arguments are supported: model (`DiffSyncModel`), ids (dictionary), and attrs (dictionary). The `model` and `ids` are used to find an existing object. If the object does not currently exist within the `Adapter` adapter, it will then use `model`, `ids`, and `attrs` to add the object. It will then return a tuple that can be unpacked. @@ -69,4 +69,4 @@ BACKEND_DATA_A = [ device.add_child(intf) ``` -The new methods are helpful due to having devices that are part of the same site. As we iterate over the data and load it into the `DiffSync` adapter, we would have to account for `ObjectAlreadyExists` exceptions when we go to add each duplicate site we encounter within the data or possibly several other models depending how complex the synchronization of data is between backends. +The new methods are helpful due to having devices that are part of the same site. As we iterate over the data and load it into the `Adapter` adapter, we would have to account for `ObjectAlreadyExists` exceptions when we go to add each duplicate site we encounter within the data or possibly several other models depending how complex the synchronization of data is between backends. diff --git a/examples/04-get-update-instantiate/backends.py b/examples/04-get-update-instantiate/backends.py index bbc06e4..ba73a8e 100644 --- a/examples/04-get-update-instantiate/backends.py +++ b/examples/04-get-update-instantiate/backends.py @@ -16,7 +16,7 @@ """ from models import Site, Device, Interface # pylint: disable=no-name-in-module -from diffsync import DiffSync +from diffsync import Adapter BACKEND_DATA_A = [ { @@ -74,7 +74,7 @@ ] -class BackendA(DiffSync): +class BackendA(Adapter): """Example of a DiffSync adapter implementation.""" site = Site @@ -104,7 +104,7 @@ def load(self): device.add_child(intf) -class BackendB(DiffSync): +class BackendB(Adapter): """Example of a DiffSync adapter implementation.""" site = Site diff --git a/examples/05-nautobot-peeringdb/adapter_nautobot.py b/examples/05-nautobot-peeringdb/adapter_nautobot.py index 7970274..84be2d6 100644 --- a/examples/05-nautobot-peeringdb/adapter_nautobot.py +++ b/examples/05-nautobot-peeringdb/adapter_nautobot.py @@ -2,7 +2,7 @@ # pylint: disable=import-error,no-name-in-module import pynautobot from models import RegionModel, SiteModel -from diffsync import DiffSync +from diffsync import Adapter class RegionNautobotModel(RegionModel): @@ -116,7 +116,7 @@ def delete(self): # pylint: disable= useless-super-delegation return super().delete() -class NautobotRemote(DiffSync): +class NautobotRemote(Adapter): """DiffSync adapter class for loading data from a remote Nautobot instance using Python requests.""" # Model classes used by this adapter class diff --git a/examples/05-nautobot-peeringdb/adapter_peeringdb.py b/examples/05-nautobot-peeringdb/adapter_peeringdb.py index 08feb60..91cdf6a 100644 --- a/examples/05-nautobot-peeringdb/adapter_peeringdb.py +++ b/examples/05-nautobot-peeringdb/adapter_peeringdb.py @@ -5,7 +5,7 @@ from slugify import slugify import pycountry from models import RegionModel, SiteModel -from diffsync import DiffSync +from diffsync import Adapter from diffsync.exceptions import ObjectNotFound @@ -13,7 +13,7 @@ PEERINGDB_API_KEY = os.environ.get("PEERINGDB_API_KEY", "").strip() -class PeeringDB(DiffSync): +class PeeringDB(Adapter): """DiffSync adapter using requests to communicate with PeeringDB.""" # Model classes used by this adapter class diff --git a/examples/06-ip-prefixes/adapter_ipam_a.py b/examples/06-ip-prefixes/adapter_ipam_a.py index 4b43595..5463cd4 100644 --- a/examples/06-ip-prefixes/adapter_ipam_a.py +++ b/examples/06-ip-prefixes/adapter_ipam_a.py @@ -3,7 +3,7 @@ import ipaddress import yaml from models import Prefix # pylint: disable=no-name-in-module -from diffsync import DiffSync +from diffsync import Adapter dirname = os.path.dirname(os.path.realpath(__file__)) @@ -50,7 +50,7 @@ def delete(self): return super().delete() -class IpamA(DiffSync): +class IpamA(Adapter): """IPAM A DiffSync adapter implementation.""" prefix = IpamAPrefix diff --git a/examples/06-ip-prefixes/adapter_ipam_b.py b/examples/06-ip-prefixes/adapter_ipam_b.py index b29e76b..7a6eadb 100644 --- a/examples/06-ip-prefixes/adapter_ipam_b.py +++ b/examples/06-ip-prefixes/adapter_ipam_b.py @@ -2,7 +2,7 @@ import os import yaml from models import Prefix # pylint: disable=no-name-in-module -from diffsync import DiffSync +from diffsync import Adapter dirname = os.path.dirname(os.path.realpath(__file__)) @@ -55,7 +55,7 @@ def delete(self): return super().delete() -class IpamB(DiffSync): +class IpamB(Adapter): """IPAM A DiffSync adapter implementation.""" prefix = IpamBPrefix diff --git a/tests/unit/conftest.py b/tests/unit/conftest.py index 365ce4a..c72d213 100644 --- a/tests/unit/conftest.py +++ b/tests/unit/conftest.py @@ -18,7 +18,7 @@ import pytest -from diffsync import DiffSync, DiffSyncModel +from diffsync import Adapter, DiffSyncModel from diffsync.diff import Diff, DiffElement from diffsync.exceptions import ObjectNotCreated, ObjectNotUpdated, ObjectNotDeleted @@ -35,7 +35,7 @@ class ErrorProneModelMixin: _counter: ClassVar[int] = 0 @classmethod - def create(cls, diffsync: DiffSync, ids: Dict, attrs: Dict): + def create(cls, diffsync: Adapter, ids: Dict, attrs: Dict): """As DiffSyncModel.create(), but periodically throw exceptions.""" cls._counter += 1 if not cls._counter % 5: @@ -69,7 +69,7 @@ class ExceptionModelMixin: """Test class that always throws exceptions when creating/updating/deleting instances.""" @classmethod - def create(cls, diffsync: DiffSync, ids: Dict, attrs: Dict): + def create(cls, diffsync: Adapter, ids: Dict, attrs: Dict): """As DiffSyncModel.create(), but always throw exceptions.""" raise NotImplementedError @@ -160,7 +160,7 @@ def interface(device_name="device1", name="eth0", **kwargs): @pytest.fixture def generic_diffsync(): """Provide a generic DiffSync instance.""" - return DiffSync() + return Adapter() class UnusedModel(DiffSyncModel): @@ -172,7 +172,7 @@ class UnusedModel(DiffSyncModel): name: str -class GenericBackend(DiffSync): +class GenericBackend(Adapter): """An example semi-abstract subclass of DiffSync.""" site = Site # to be overridden by subclasses diff --git a/tests/unit/test_diffsync.py b/tests/unit/test_diffsync.py index 74727be..824b0f4 100644 --- a/tests/unit/test_diffsync.py +++ b/tests/unit/test_diffsync.py @@ -5,7 +5,7 @@ import pytest -from diffsync import DiffSync, DiffSyncModel +from diffsync import Adapter, DiffSyncModel from diffsync.enum import DiffSyncFlags, DiffSyncModelFlags from diffsync.exceptions import DiffClassMismatch, ObjectAlreadyExists, ObjectNotFound, ObjectCrudException @@ -351,7 +351,7 @@ def test_diffsync_subclass_validation_name_mismatch(): # pylint: disable=unused-variable with pytest.raises(AttributeError) as excinfo: - class BadElementName(DiffSync): + class BadElementName(Adapter): """DiffSync with a DiffSyncModel attribute whose name does not match the modelname.""" dev_class = Device # should be device = Device @@ -365,7 +365,7 @@ def test_diffsync_subclass_validation_missing_top_level(): # pylint: disable=unused-variable with pytest.raises(AttributeError) as excinfo: - class MissingTopLevel(DiffSync): + class MissingTopLevel(Adapter): """DiffSync whose top_level references an attribute that does not exist on the class.""" top_level = ["missing"] @@ -379,7 +379,7 @@ def test_diffsync_subclass_validation_top_level_not_diffsyncmodel(): # pylint: disable=unused-variable with pytest.raises(AttributeError) as excinfo: - class TopLevelNotDiffSyncModel(DiffSync): + class TopLevelNotDiffSyncModel(Adapter): """DiffSync whose top_level references an attribute that is not a DiffSyncModel subclass.""" age = 0 diff --git a/tests/unit/test_diffsync_model_flags.py b/tests/unit/test_diffsync_model_flags.py index 8c10499..3217f44 100644 --- a/tests/unit/test_diffsync_model_flags.py +++ b/tests/unit/test_diffsync_model_flags.py @@ -18,7 +18,7 @@ import pytest -from diffsync import DiffSync, DiffSyncModel +from diffsync import Adapter, DiffSyncModel from diffsync.enum import DiffSyncModelFlags from diffsync.exceptions import ObjectNotFound @@ -141,7 +141,7 @@ def delete(self): call_order.append(self.name) return super().delete() - class TestBackend(DiffSync): # pylint: disable=missing-class-docstring + class TestBackend(Adapter): # pylint: disable=missing-class-docstring top_level = ["parent"] parent = TestModelParent From 228942c14a6c215e4d2fa0d13667bdc65042c48e Mon Sep 17 00:00:00 2001 From: Leo Kirchner Date: Thu, 24 Aug 2023 17:06:48 -0600 Subject: [PATCH 06/10] pins poetry in Dockerfile to 1.5.1 --- .github/workflows/ci.yml | 4 +++- Dockerfile | 2 +- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 379b797..55c77e3 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -130,6 +130,7 @@ jobs: fail-fast: true matrix: python-version: ["3.8", "3.9", "3.10", "3.11"] + poetry-version: ["1.5.1"] runs-on: "ubuntu-20.04" env: PYTHON_VER: "${{ matrix.python-version }}" @@ -137,11 +138,12 @@ jobs: - name: "Check out repository code" uses: "actions/checkout@v2" - name: "Setup environment" - uses: "networktocode/gh-action-setup-poetry-environment@v5" + uses: "networktocode/gh-action-setup-poetry-environment@3ea5d3e" env: POETRY_VERSION: 1.5.1 with: python-version: "${{ matrix.python-version }}" + poetry-version: "${{ matrix.poetry-version }}" - name: "Install redis" run: "sudo apt-get install -y redis" - name: "Run poetry Install" diff --git a/Dockerfile b/Dockerfile index 824f054..e5b36ce 100644 --- a/Dockerfile +++ b/Dockerfile @@ -8,7 +8,7 @@ RUN apt-get update && apt-get install -y --no-install-recommends \ && rm -rf /var/lib/apt/lists/* RUN pip install --upgrade pip \ - && pip install poetry + && pip install poetry==1.5.1 WORKDIR /local From 851907e00a68719939a55978a42371e333c2d092 Mon Sep 17 00:00:00 2001 From: Leo Kirchner Date: Thu, 31 Aug 2023 12:31:46 -0600 Subject: [PATCH 07/10] fix ci --- .github/workflows/ci.yml | 2 +- tests/unit/test_diffsync.py | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 55c77e3..241845b 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -138,7 +138,7 @@ jobs: - name: "Check out repository code" uses: "actions/checkout@v2" - name: "Setup environment" - uses: "networktocode/gh-action-setup-poetry-environment@3ea5d3e" + uses: "networktocode/gh-action-setup-poetry-environment@3ea5d3ecf382cdcb0c74d4c0ff0629d95fce63c7" env: POETRY_VERSION: 1.5.1 with: diff --git a/tests/unit/test_diffsync.py b/tests/unit/test_diffsync.py index 824b0f4..6f7feae 100644 --- a/tests/unit/test_diffsync.py +++ b/tests/unit/test_diffsync.py @@ -13,8 +13,8 @@ def test_diffsync_default_name_type(generic_diffsync): - assert generic_diffsync.type == "DiffSync" - assert generic_diffsync.name == "DiffSync" + assert generic_diffsync.type == "Adapter" + assert generic_diffsync.name == "Adapter" def test_diffsync_generic_load_is_noop(generic_diffsync): From f5ed5446a20162b607cc4204aec7f99924381753 Mon Sep 17 00:00:00 2001 From: Leo Kirchner Date: Mon, 22 Jan 2024 08:51:28 +0100 Subject: [PATCH 08/10] bumps major version to 2.0.0 --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 8fac988..2f48372 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "diffsync" -version = "1.10.0" +version = "2.0.0" description = "Library to easily sync/diff/update 2 different data sources" authors = ["Network to Code, LLC "] license = "Apache-2.0" From 692856571f7175f5951b6fd409b4a90753f5e2e9 Mon Sep 17 00:00:00 2001 From: Leo Kirchner Date: Mon, 22 Jan 2024 09:00:13 +0100 Subject: [PATCH 09/10] various 2.0 preparation things --- .flake8 | 1 + diffsync/__init__.py | 14 +- docs/source/upgrading/01-upgrading-to-2.0.md | 6 +- poetry.lock | 184 ++++++++++++++----- tests/unit/test_diffsync_model_flags.py | 6 +- 5 files changed, 161 insertions(+), 50 deletions(-) diff --git a/.flake8 b/.flake8 index e3ba27d..f1227b1 100644 --- a/.flake8 +++ b/.flake8 @@ -2,3 +2,4 @@ # E501: Line length is enforced by Black, so flake8 doesn't need to check it # W503: Black disagrees with this rule, as does PEP 8; Black wins ignore = E501, W503 +exclude = .venv \ No newline at end of file diff --git a/diffsync/__init__.py b/diffsync/__init__.py index 39e7b2b..46194b9 100644 --- a/diffsync/__init__.py +++ b/diffsync/__init__.py @@ -17,6 +17,7 @@ import sys from inspect import isclass from typing import Callable, ClassVar, Dict, List, Optional, Tuple, Type, Union, Any, Set +import warnings from pydantic import ConfigDict, BaseModel, PrivateAttr import structlog # type: ignore @@ -854,8 +855,15 @@ def count(self, model: Union[StrType, "DiffSyncModel", Type["DiffSyncModel"], No return self.store.count(model=model) -# For backwards-compatibility, keep around the old name -DiffSync = Adapter +def DiffSync(*args: Any, **kwargs: Any) -> Adapter: # noqa pylint: disable=invalid-name + """For backwards-compatibility, keep around the old name.""" -# DiffSyncModel references DiffSync and DiffSync references DiffSyncModel. Break the typing loop: + warnings.warn( + "'diffsync.DiffSync' is deprecated and will be removed with 2.1, use 'diffsync.Adapter' instead.", + DeprecationWarning, + ) + return Adapter(*args, **kwargs) + + +# DiffSyncModel references Adapter and Adapter references DiffSyncModel. Break the typing loop: DiffSyncModel.model_rebuild() diff --git a/docs/source/upgrading/01-upgrading-to-2.0.md b/docs/source/upgrading/01-upgrading-to-2.0.md index 10a7b08..55e896b 100644 --- a/docs/source/upgrading/01-upgrading-to-2.0.md +++ b/docs/source/upgrading/01-upgrading-to-2.0.md @@ -2,6 +2,10 @@ With diffsync 2.0, there a couple of breaking changes. What they are and how to deal with them is described in this document. +## Rename of the `diffsync.Diffsync` class to `diffsync.Adapter` + +The main diffsync class `diffsync.Diffsync` has been renamed to `diffsync.Adapter` as we have found that this is the verbiage that is most often used by users and explains the intent of the class clearer. The old name will still be around until 2.1, but is considered deprecated at this point. + ## Upgrade to Pydantic's major version 2 A [migration guide](https://docs.pydantic.dev/latest/migration/) is available in the Pydantic documentation. Here are the key things that may apply to your usage of diffsync: @@ -22,7 +26,7 @@ class Person(DiffSyncModel): age: Optional[int] # After -class BetterPerson(DiffSyncModel) +class BetterPerson(DiffSyncModel): _identifiers = ("name",) _attributes = ("age",) diff --git a/poetry.lock b/poetry.lock index 50f846c..2972018 100644 --- a/poetry.lock +++ b/poetry.lock @@ -1,4 +1,4 @@ -# This file is automatically @generated by Poetry 1.6.1 and should not be changed by hand. +# This file is automatically @generated by Poetry 1.7.1 and should not be changed by hand. [[package]] name = "alabaster" @@ -11,6 +11,20 @@ files = [ {file = "alabaster-0.7.13.tar.gz", hash = "sha256:a27a4a084d5e690e16e01e03ad2b2e552c61a65469419b907243193de1a84ae2"}, ] +[[package]] +name = "annotated-types" +version = "0.6.0" +description = "Reusable constraint types to use with typing.Annotated" +optional = false +python-versions = ">=3.8" +files = [ + {file = "annotated_types-0.6.0-py3-none-any.whl", hash = "sha256:0641064de18ba7a25dee8f96403ebc39113d0cb953a01429249d5c7564666a43"}, + {file = "annotated_types-0.6.0.tar.gz", hash = "sha256:563339e807e53ffd9c267e99fc6d9ea23eb8443c08f112651963e24e22f84a5d"}, +] + +[package.dependencies] +typing-extensions = {version = ">=4.0.0", markers = "python_version < \"3.9\""} + [[package]] name = "astroid" version = "2.11.7" @@ -959,55 +973,139 @@ files = [ [[package]] name = "pydantic" -version = "1.10.5" -description = "Data validation and settings management using python type hints" +version = "2.5.3" +description = "Data validation using Python type hints" optional = false python-versions = ">=3.7" files = [ - {file = "pydantic-1.10.5-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:5920824fe1e21cbb3e38cf0f3dd24857c8959801d1031ce1fac1d50857a03bfb"}, - {file = "pydantic-1.10.5-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:3bb99cf9655b377db1a9e47fa4479e3330ea96f4123c6c8200e482704bf1eda2"}, - {file = "pydantic-1.10.5-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2185a3b3d98ab4506a3f6707569802d2d92c3a7ba3a9a35683a7709ea6c2aaa2"}, - {file = "pydantic-1.10.5-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f582cac9d11c227c652d3ce8ee223d94eb06f4228b52a8adaafa9fa62e73d5c9"}, - {file = "pydantic-1.10.5-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:c9e5b778b6842f135902e2d82624008c6a79710207e28e86966cd136c621bfee"}, - {file = "pydantic-1.10.5-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:72ef3783be8cbdef6bca034606a5de3862be6b72415dc5cb1fb8ddbac110049a"}, - {file = "pydantic-1.10.5-cp310-cp310-win_amd64.whl", hash = "sha256:45edea10b75d3da43cfda12f3792833a3fa70b6eee4db1ed6aed528cef17c74e"}, - {file = "pydantic-1.10.5-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:63200cd8af1af2c07964546b7bc8f217e8bda9d0a2ef0ee0c797b36353914984"}, - {file = "pydantic-1.10.5-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:305d0376c516b0dfa1dbefeae8c21042b57b496892d721905a6ec6b79494a66d"}, - {file = "pydantic-1.10.5-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1fd326aff5d6c36f05735c7c9b3d5b0e933b4ca52ad0b6e4b38038d82703d35b"}, - {file = "pydantic-1.10.5-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6bb0452d7b8516178c969d305d9630a3c9b8cf16fcf4713261c9ebd465af0d73"}, - {file = "pydantic-1.10.5-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:9a9d9155e2a9f38b2eb9374c88f02fd4d6851ae17b65ee786a87d032f87008f8"}, - {file = "pydantic-1.10.5-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:f836444b4c5ece128b23ec36a446c9ab7f9b0f7981d0d27e13a7c366ee163f8a"}, - {file = "pydantic-1.10.5-cp311-cp311-win_amd64.whl", hash = "sha256:8481dca324e1c7b715ce091a698b181054d22072e848b6fc7895cd86f79b4449"}, - {file = "pydantic-1.10.5-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:87f831e81ea0589cd18257f84386bf30154c5f4bed373b7b75e5cb0b5d53ea87"}, - {file = "pydantic-1.10.5-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7ce1612e98c6326f10888df951a26ec1a577d8df49ddcaea87773bfbe23ba5cc"}, - {file = "pydantic-1.10.5-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:58e41dd1e977531ac6073b11baac8c013f3cd8706a01d3dc74e86955be8b2c0c"}, - {file = "pydantic-1.10.5-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:6a4b0aab29061262065bbdede617ef99cc5914d1bf0ddc8bcd8e3d7928d85bd6"}, - {file = "pydantic-1.10.5-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:36e44a4de37b8aecffa81c081dbfe42c4d2bf9f6dff34d03dce157ec65eb0f15"}, - {file = "pydantic-1.10.5-cp37-cp37m-win_amd64.whl", hash = "sha256:261f357f0aecda005934e413dfd7aa4077004a174dafe414a8325e6098a8e419"}, - {file = "pydantic-1.10.5-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:b429f7c457aebb7fbe7cd69c418d1cd7c6fdc4d3c8697f45af78b8d5a7955760"}, - {file = "pydantic-1.10.5-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:663d2dd78596c5fa3eb996bc3f34b8c2a592648ad10008f98d1348be7ae212fb"}, - {file = "pydantic-1.10.5-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:51782fd81f09edcf265823c3bf43ff36d00db246eca39ee765ef58dc8421a642"}, - {file = "pydantic-1.10.5-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c428c0f64a86661fb4873495c4fac430ec7a7cef2b8c1c28f3d1a7277f9ea5ab"}, - {file = "pydantic-1.10.5-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:76c930ad0746c70f0368c4596020b736ab65b473c1f9b3872310a835d852eb19"}, - {file = "pydantic-1.10.5-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:3257bd714de9db2102b742570a56bf7978e90441193acac109b1f500290f5718"}, - {file = "pydantic-1.10.5-cp38-cp38-win_amd64.whl", hash = "sha256:f5bee6c523d13944a1fdc6f0525bc86dbbd94372f17b83fa6331aabacc8fd08e"}, - {file = "pydantic-1.10.5-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:532e97c35719f137ee5405bd3eeddc5c06eb91a032bc755a44e34a712420daf3"}, - {file = "pydantic-1.10.5-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:ca9075ab3de9e48b75fa8ccb897c34ccc1519177ad8841d99f7fd74cf43be5bf"}, - {file = "pydantic-1.10.5-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bd46a0e6296346c477e59a954da57beaf9c538da37b9df482e50f836e4a7d4bb"}, - {file = "pydantic-1.10.5-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3353072625ea2a9a6c81ad01b91e5c07fa70deb06368c71307529abf70d23325"}, - {file = "pydantic-1.10.5-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:3f9d9b2be177c3cb6027cd67fbf323586417868c06c3c85d0d101703136e6b31"}, - {file = "pydantic-1.10.5-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:b473d00ccd5c2061fd896ac127b7755baad233f8d996ea288af14ae09f8e0d1e"}, - {file = "pydantic-1.10.5-cp39-cp39-win_amd64.whl", hash = "sha256:5f3bc8f103b56a8c88021d481410874b1f13edf6e838da607dcb57ecff9b4594"}, - {file = "pydantic-1.10.5-py3-none-any.whl", hash = "sha256:7c5b94d598c90f2f46b3a983ffb46ab806a67099d118ae0da7ef21a2a4033b28"}, - {file = "pydantic-1.10.5.tar.gz", hash = "sha256:9e337ac83686645a46db0e825acceea8e02fca4062483f40e9ae178e8bd1103a"}, + {file = "pydantic-2.5.3-py3-none-any.whl", hash = "sha256:d0caf5954bee831b6bfe7e338c32b9e30c85dfe080c843680783ac2b631673b4"}, + {file = "pydantic-2.5.3.tar.gz", hash = "sha256:b3ef57c62535b0941697cce638c08900d87fcb67e29cfa99e8a68f747f393f7a"}, ] [package.dependencies] -typing-extensions = ">=4.2.0" +annotated-types = ">=0.4.0" +pydantic-core = "2.14.6" +typing-extensions = ">=4.6.1" [package.extras] -dotenv = ["python-dotenv (>=0.10.4)"] -email = ["email-validator (>=1.0.3)"] +email = ["email-validator (>=2.0.0)"] + +[[package]] +name = "pydantic-core" +version = "2.14.6" +description = "" +optional = false +python-versions = ">=3.7" +files = [ + {file = "pydantic_core-2.14.6-cp310-cp310-macosx_10_7_x86_64.whl", hash = "sha256:72f9a942d739f09cd42fffe5dc759928217649f070056f03c70df14f5770acf9"}, + {file = "pydantic_core-2.14.6-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:6a31d98c0d69776c2576dda4b77b8e0c69ad08e8b539c25c7d0ca0dc19a50d6c"}, + {file = "pydantic_core-2.14.6-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5aa90562bc079c6c290f0512b21768967f9968e4cfea84ea4ff5af5d917016e4"}, + {file = "pydantic_core-2.14.6-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:370ffecb5316ed23b667d99ce4debe53ea664b99cc37bfa2af47bc769056d534"}, + {file = "pydantic_core-2.14.6-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f85f3843bdb1fe80e8c206fe6eed7a1caeae897e496542cee499c374a85c6e08"}, + {file = "pydantic_core-2.14.6-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9862bf828112e19685b76ca499b379338fd4c5c269d897e218b2ae8fcb80139d"}, + {file = "pydantic_core-2.14.6-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:036137b5ad0cb0004c75b579445a1efccd072387a36c7f217bb8efd1afbe5245"}, + {file = "pydantic_core-2.14.6-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:92879bce89f91f4b2416eba4429c7b5ca22c45ef4a499c39f0c5c69257522c7c"}, + {file = "pydantic_core-2.14.6-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:0c08de15d50fa190d577e8591f0329a643eeaed696d7771760295998aca6bc66"}, + {file = "pydantic_core-2.14.6-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:36099c69f6b14fc2c49d7996cbf4f87ec4f0e66d1c74aa05228583225a07b590"}, + {file = "pydantic_core-2.14.6-cp310-none-win32.whl", hash = "sha256:7be719e4d2ae6c314f72844ba9d69e38dff342bc360379f7c8537c48e23034b7"}, + {file = "pydantic_core-2.14.6-cp310-none-win_amd64.whl", hash = "sha256:36fa402dcdc8ea7f1b0ddcf0df4254cc6b2e08f8cd80e7010d4c4ae6e86b2a87"}, + {file = "pydantic_core-2.14.6-cp311-cp311-macosx_10_7_x86_64.whl", hash = "sha256:dea7fcd62915fb150cdc373212141a30037e11b761fbced340e9db3379b892d4"}, + {file = "pydantic_core-2.14.6-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:ffff855100bc066ff2cd3aa4a60bc9534661816b110f0243e59503ec2df38421"}, + {file = "pydantic_core-2.14.6-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1b027c86c66b8627eb90e57aee1f526df77dc6d8b354ec498be9a757d513b92b"}, + {file = "pydantic_core-2.14.6-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:00b1087dabcee0b0ffd104f9f53d7d3eaddfaa314cdd6726143af6bc713aa27e"}, + {file = "pydantic_core-2.14.6-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:75ec284328b60a4e91010c1acade0c30584f28a1f345bc8f72fe8b9e46ec6a96"}, + {file = "pydantic_core-2.14.6-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:7e1f4744eea1501404b20b0ac059ff7e3f96a97d3e3f48ce27a139e053bb370b"}, + {file = "pydantic_core-2.14.6-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b2602177668f89b38b9f84b7b3435d0a72511ddef45dc14446811759b82235a1"}, + {file = "pydantic_core-2.14.6-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:6c8edaea3089bf908dd27da8f5d9e395c5b4dc092dbcce9b65e7156099b4b937"}, + {file = "pydantic_core-2.14.6-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:478e9e7b360dfec451daafe286998d4a1eeaecf6d69c427b834ae771cad4b622"}, + {file = "pydantic_core-2.14.6-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:b6ca36c12a5120bad343eef193cc0122928c5c7466121da7c20f41160ba00ba2"}, + {file = "pydantic_core-2.14.6-cp311-none-win32.whl", hash = "sha256:2b8719037e570639e6b665a4050add43134d80b687288ba3ade18b22bbb29dd2"}, + {file = "pydantic_core-2.14.6-cp311-none-win_amd64.whl", hash = "sha256:78ee52ecc088c61cce32b2d30a826f929e1708f7b9247dc3b921aec367dc1b23"}, + {file = "pydantic_core-2.14.6-cp311-none-win_arm64.whl", hash = "sha256:a19b794f8fe6569472ff77602437ec4430f9b2b9ec7a1105cfd2232f9ba355e6"}, + {file = "pydantic_core-2.14.6-cp312-cp312-macosx_10_7_x86_64.whl", hash = "sha256:667aa2eac9cd0700af1ddb38b7b1ef246d8cf94c85637cbb03d7757ca4c3fdec"}, + {file = "pydantic_core-2.14.6-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:cdee837710ef6b56ebd20245b83799fce40b265b3b406e51e8ccc5b85b9099b7"}, + {file = "pydantic_core-2.14.6-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2c5bcf3414367e29f83fd66f7de64509a8fd2368b1edf4351e862910727d3e51"}, + {file = "pydantic_core-2.14.6-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:26a92ae76f75d1915806b77cf459811e772d8f71fd1e4339c99750f0e7f6324f"}, + {file = "pydantic_core-2.14.6-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a983cca5ed1dd9a35e9e42ebf9f278d344603bfcb174ff99a5815f953925140a"}, + {file = "pydantic_core-2.14.6-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:cb92f9061657287eded380d7dc455bbf115430b3aa4741bdc662d02977e7d0af"}, + {file = "pydantic_core-2.14.6-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e4ace1e220b078c8e48e82c081e35002038657e4b37d403ce940fa679e57113b"}, + {file = "pydantic_core-2.14.6-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:ef633add81832f4b56d3b4c9408b43d530dfca29e68fb1b797dcb861a2c734cd"}, + {file = "pydantic_core-2.14.6-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:7e90d6cc4aad2cc1f5e16ed56e46cebf4877c62403a311af20459c15da76fd91"}, + {file = "pydantic_core-2.14.6-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:e8a5ac97ea521d7bde7621d86c30e86b798cdecd985723c4ed737a2aa9e77d0c"}, + {file = "pydantic_core-2.14.6-cp312-none-win32.whl", hash = "sha256:f27207e8ca3e5e021e2402ba942e5b4c629718e665c81b8b306f3c8b1ddbb786"}, + {file = "pydantic_core-2.14.6-cp312-none-win_amd64.whl", hash = "sha256:b3e5fe4538001bb82e2295b8d2a39356a84694c97cb73a566dc36328b9f83b40"}, + {file = "pydantic_core-2.14.6-cp312-none-win_arm64.whl", hash = "sha256:64634ccf9d671c6be242a664a33c4acf12882670b09b3f163cd00a24cffbd74e"}, + {file = "pydantic_core-2.14.6-cp37-cp37m-macosx_10_7_x86_64.whl", hash = "sha256:24368e31be2c88bd69340fbfe741b405302993242ccb476c5c3ff48aeee1afe0"}, + {file = "pydantic_core-2.14.6-cp37-cp37m-macosx_11_0_arm64.whl", hash = "sha256:e33b0834f1cf779aa839975f9d8755a7c2420510c0fa1e9fa0497de77cd35d2c"}, + {file = "pydantic_core-2.14.6-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6af4b3f52cc65f8a0bc8b1cd9676f8c21ef3e9132f21fed250f6958bd7223bed"}, + {file = "pydantic_core-2.14.6-cp37-cp37m-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:d15687d7d7f40333bd8266f3814c591c2e2cd263fa2116e314f60d82086e353a"}, + {file = "pydantic_core-2.14.6-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:095b707bb287bfd534044166ab767bec70a9bba3175dcdc3371782175c14e43c"}, + {file = "pydantic_core-2.14.6-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:94fc0e6621e07d1e91c44e016cc0b189b48db053061cc22d6298a611de8071bb"}, + {file = "pydantic_core-2.14.6-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1ce830e480f6774608dedfd4a90c42aac4a7af0a711f1b52f807130c2e434c06"}, + {file = "pydantic_core-2.14.6-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:a306cdd2ad3a7d795d8e617a58c3a2ed0f76c8496fb7621b6cd514eb1532cae8"}, + {file = "pydantic_core-2.14.6-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:2f5fa187bde8524b1e37ba894db13aadd64faa884657473b03a019f625cee9a8"}, + {file = "pydantic_core-2.14.6-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:438027a975cc213a47c5d70672e0d29776082155cfae540c4e225716586be75e"}, + {file = "pydantic_core-2.14.6-cp37-none-win32.whl", hash = "sha256:f96ae96a060a8072ceff4cfde89d261837b4294a4f28b84a28765470d502ccc6"}, + {file = "pydantic_core-2.14.6-cp37-none-win_amd64.whl", hash = "sha256:e646c0e282e960345314f42f2cea5e0b5f56938c093541ea6dbf11aec2862391"}, + {file = "pydantic_core-2.14.6-cp38-cp38-macosx_10_7_x86_64.whl", hash = "sha256:db453f2da3f59a348f514cfbfeb042393b68720787bbef2b4c6068ea362c8149"}, + {file = "pydantic_core-2.14.6-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:3860c62057acd95cc84044e758e47b18dcd8871a328ebc8ccdefd18b0d26a21b"}, + {file = "pydantic_core-2.14.6-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:36026d8f99c58d7044413e1b819a67ca0e0b8ebe0f25e775e6c3d1fabb3c38fb"}, + {file = "pydantic_core-2.14.6-cp38-cp38-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:8ed1af8692bd8d2a29d702f1a2e6065416d76897d726e45a1775b1444f5928a7"}, + {file = "pydantic_core-2.14.6-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:314ccc4264ce7d854941231cf71b592e30d8d368a71e50197c905874feacc8a8"}, + {file = "pydantic_core-2.14.6-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:982487f8931067a32e72d40ab6b47b1628a9c5d344be7f1a4e668fb462d2da42"}, + {file = "pydantic_core-2.14.6-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2dbe357bc4ddda078f79d2a36fc1dd0494a7f2fad83a0a684465b6f24b46fe80"}, + {file = "pydantic_core-2.14.6-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:2f6ffc6701a0eb28648c845f4945a194dc7ab3c651f535b81793251e1185ac3d"}, + {file = "pydantic_core-2.14.6-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:7f5025db12fc6de7bc1104d826d5aee1d172f9ba6ca936bf6474c2148ac336c1"}, + {file = "pydantic_core-2.14.6-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:dab03ed811ed1c71d700ed08bde8431cf429bbe59e423394f0f4055f1ca0ea60"}, + {file = "pydantic_core-2.14.6-cp38-none-win32.whl", hash = "sha256:dfcbebdb3c4b6f739a91769aea5ed615023f3c88cb70df812849aef634c25fbe"}, + {file = "pydantic_core-2.14.6-cp38-none-win_amd64.whl", hash = "sha256:99b14dbea2fdb563d8b5a57c9badfcd72083f6006caf8e126b491519c7d64ca8"}, + {file = "pydantic_core-2.14.6-cp39-cp39-macosx_10_7_x86_64.whl", hash = "sha256:4ce8299b481bcb68e5c82002b96e411796b844d72b3e92a3fbedfe8e19813eab"}, + {file = "pydantic_core-2.14.6-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:b9a9d92f10772d2a181b5ca339dee066ab7d1c9a34ae2421b2a52556e719756f"}, + {file = "pydantic_core-2.14.6-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:fd9e98b408384989ea4ab60206b8e100d8687da18b5c813c11e92fd8212a98e0"}, + {file = "pydantic_core-2.14.6-cp39-cp39-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:4f86f1f318e56f5cbb282fe61eb84767aee743ebe32c7c0834690ebea50c0a6b"}, + {file = "pydantic_core-2.14.6-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:86ce5fcfc3accf3a07a729779d0b86c5d0309a4764c897d86c11089be61da160"}, + {file = "pydantic_core-2.14.6-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3dcf1978be02153c6a31692d4fbcc2a3f1db9da36039ead23173bc256ee3b91b"}, + {file = "pydantic_core-2.14.6-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:eedf97be7bc3dbc8addcef4142f4b4164066df0c6f36397ae4aaed3eb187d8ab"}, + {file = "pydantic_core-2.14.6-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:d5f916acf8afbcab6bacbb376ba7dc61f845367901ecd5e328fc4d4aef2fcab0"}, + {file = "pydantic_core-2.14.6-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:8a14c192c1d724c3acbfb3f10a958c55a2638391319ce8078cb36c02283959b9"}, + {file = "pydantic_core-2.14.6-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:0348b1dc6b76041516e8a854ff95b21c55f5a411c3297d2ca52f5528e49d8411"}, + {file = "pydantic_core-2.14.6-cp39-none-win32.whl", hash = "sha256:de2a0645a923ba57c5527497daf8ec5df69c6eadf869e9cd46e86349146e5975"}, + {file = "pydantic_core-2.14.6-cp39-none-win_amd64.whl", hash = "sha256:aca48506a9c20f68ee61c87f2008f81f8ee99f8d7f0104bff3c47e2d148f89d9"}, + {file = "pydantic_core-2.14.6-pp310-pypy310_pp73-macosx_10_7_x86_64.whl", hash = "sha256:d5c28525c19f5bb1e09511669bb57353d22b94cf8b65f3a8d141c389a55dec95"}, + {file = "pydantic_core-2.14.6-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:78d0768ee59baa3de0f4adac9e3748b4b1fffc52143caebddfd5ea2961595277"}, + {file = "pydantic_core-2.14.6-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8b93785eadaef932e4fe9c6e12ba67beb1b3f1e5495631419c784ab87e975670"}, + {file = "pydantic_core-2.14.6-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a874f21f87c485310944b2b2734cd6d318765bcbb7515eead33af9641816506e"}, + {file = "pydantic_core-2.14.6-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:b89f4477d915ea43b4ceea6756f63f0288941b6443a2b28c69004fe07fde0d0d"}, + {file = "pydantic_core-2.14.6-pp310-pypy310_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:172de779e2a153d36ee690dbc49c6db568d7b33b18dc56b69a7514aecbcf380d"}, + {file = "pydantic_core-2.14.6-pp310-pypy310_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:dfcebb950aa7e667ec226a442722134539e77c575f6cfaa423f24371bb8d2e94"}, + {file = "pydantic_core-2.14.6-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:55a23dcd98c858c0db44fc5c04fc7ed81c4b4d33c653a7c45ddaebf6563a2f66"}, + {file = "pydantic_core-2.14.6-pp37-pypy37_pp73-macosx_10_7_x86_64.whl", hash = "sha256:4241204e4b36ab5ae466ecec5c4c16527a054c69f99bba20f6f75232a6a534e2"}, + {file = "pydantic_core-2.14.6-pp37-pypy37_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e574de99d735b3fc8364cba9912c2bec2da78775eba95cbb225ef7dda6acea24"}, + {file = "pydantic_core-2.14.6-pp37-pypy37_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1302a54f87b5cd8528e4d6d1bf2133b6aa7c6122ff8e9dc5220fbc1e07bffebd"}, + {file = "pydantic_core-2.14.6-pp37-pypy37_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:f8e81e4b55930e5ffab4a68db1af431629cf2e4066dbdbfef65348b8ab804ea8"}, + {file = "pydantic_core-2.14.6-pp37-pypy37_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:c99462ffc538717b3e60151dfaf91125f637e801f5ab008f81c402f1dff0cd0f"}, + {file = "pydantic_core-2.14.6-pp37-pypy37_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:e4cf2d5829f6963a5483ec01578ee76d329eb5caf330ecd05b3edd697e7d768a"}, + {file = "pydantic_core-2.14.6-pp38-pypy38_pp73-macosx_10_7_x86_64.whl", hash = "sha256:cf10b7d58ae4a1f07fccbf4a0a956d705356fea05fb4c70608bb6fa81d103cda"}, + {file = "pydantic_core-2.14.6-pp38-pypy38_pp73-macosx_11_0_arm64.whl", hash = "sha256:399ac0891c284fa8eb998bcfa323f2234858f5d2efca3950ae58c8f88830f145"}, + {file = "pydantic_core-2.14.6-pp38-pypy38_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9c6a5c79b28003543db3ba67d1df336f253a87d3112dac3a51b94f7d48e4c0e1"}, + {file = "pydantic_core-2.14.6-pp38-pypy38_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:599c87d79cab2a6a2a9df4aefe0455e61e7d2aeede2f8577c1b7c0aec643ee8e"}, + {file = "pydantic_core-2.14.6-pp38-pypy38_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:43e166ad47ba900f2542a80d83f9fc65fe99eb63ceec4debec160ae729824052"}, + {file = "pydantic_core-2.14.6-pp38-pypy38_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:3a0b5db001b98e1c649dd55afa928e75aa4087e587b9524a4992316fa23c9fba"}, + {file = "pydantic_core-2.14.6-pp38-pypy38_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:747265448cb57a9f37572a488a57d873fd96bf51e5bb7edb52cfb37124516da4"}, + {file = "pydantic_core-2.14.6-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:7ebe3416785f65c28f4f9441e916bfc8a54179c8dea73c23023f7086fa601c5d"}, + {file = "pydantic_core-2.14.6-pp39-pypy39_pp73-macosx_10_7_x86_64.whl", hash = "sha256:86c963186ca5e50d5c8287b1d1c9d3f8f024cbe343d048c5bd282aec2d8641f2"}, + {file = "pydantic_core-2.14.6-pp39-pypy39_pp73-macosx_11_0_arm64.whl", hash = "sha256:e0641b506486f0b4cd1500a2a65740243e8670a2549bb02bc4556a83af84ae03"}, + {file = "pydantic_core-2.14.6-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:71d72ca5eaaa8d38c8df16b7deb1a2da4f650c41b58bb142f3fb75d5ad4a611f"}, + {file = "pydantic_core-2.14.6-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:27e524624eace5c59af499cd97dc18bb201dc6a7a2da24bfc66ef151c69a5f2a"}, + {file = "pydantic_core-2.14.6-pp39-pypy39_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:a3dde6cac75e0b0902778978d3b1646ca9f438654395a362cb21d9ad34b24acf"}, + {file = "pydantic_core-2.14.6-pp39-pypy39_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:00646784f6cd993b1e1c0e7b0fdcbccc375d539db95555477771c27555e3c556"}, + {file = "pydantic_core-2.14.6-pp39-pypy39_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:23598acb8ccaa3d1d875ef3b35cb6376535095e9405d91a3d57a8c7db5d29341"}, + {file = "pydantic_core-2.14.6-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:7f41533d7e3cf9520065f610b41ac1c76bc2161415955fbcead4981b22c7611e"}, + {file = "pydantic_core-2.14.6.tar.gz", hash = "sha256:1fd0c1d395372843fba13a51c28e3bb9d59bd7aebfeb17358ffaaa1e4dbbe948"}, +] + +[package.dependencies] +typing-extensions = ">=4.6.0,<4.7.0 || >4.7.0" [[package]] name = "pydocstyle" @@ -1691,4 +1789,4 @@ redis = ["redis"] [metadata] lock-version = "2.0" python-versions = ">=3.8,<4.0" -content-hash = "d9fde9c831f492bddd25a304bee6047d730b94b2e3240325cf456e24b84aa811" +content-hash = "4e607b247cbc76d2f676733677c182e154883d78d16a227081b4f1036d4690fa" diff --git a/tests/unit/test_diffsync_model_flags.py b/tests/unit/test_diffsync_model_flags.py index 3217f44..cc6f253 100644 --- a/tests/unit/test_diffsync_model_flags.py +++ b/tests/unit/test_diffsync_model_flags.py @@ -194,7 +194,7 @@ class ParentModel(DiffSyncModel): attribute: str children: List[ChildModel] = [] - class Adapter(DiffSync): + class TestAdapter(Adapter): """Test adapter.""" top_level = ["parent"] @@ -218,9 +218,9 @@ def load(self, is_source=False) -> None: parent.add_child(child) self.add(child) - source_adapter = Adapter() + source_adapter = TestAdapter() source_adapter.load(is_source=True) - destination_adapter = Adapter() + destination_adapter = TestAdapter() destination_adapter.load() source_adapter.sync_to(destination_adapter) From 615ed7c100a487622f53daa1b9082d5eda26d44a Mon Sep 17 00:00:00 2001 From: Leo Kirchner Date: Tue, 23 Jan 2024 09:04:02 +0100 Subject: [PATCH 10/10] renames 'diffsync' fields and variables to 'adapter' --- diffsync/__init__.py | 89 +++++-- diffsync/helpers.py | 2 +- diffsync/store/__init__.py | 12 +- diffsync/store/local.py | 4 +- diffsync/store/redis.py | 6 +- .../getting_started/01-getting-started.md | 4 +- docs/source/upgrading/01-upgrading-to-2.0.md | 2 + examples/03-remote-system/nautobot_models.py | 14 +- .../05-nautobot-peeringdb/adapter_nautobot.py | 24 +- examples/06-ip-prefixes/adapter_ipam_a.py | 12 +- examples/06-ip-prefixes/adapter_ipam_b.py | 12 +- tests/unit/conftest.py | 12 +- tests/unit/test_diffsync.py | 246 +++++++++--------- tests/unit/test_diffsync_model.py | 46 ++-- 14 files changed, 263 insertions(+), 222 deletions(-) diff --git a/diffsync/__init__.py b/diffsync/__init__.py index 46194b9..acaf732 100644 --- a/diffsync/__init__.py +++ b/diffsync/__init__.py @@ -16,7 +16,18 @@ """ import sys from inspect import isclass -from typing import Callable, ClassVar, Dict, List, Optional, Tuple, Type, Union, Any, Set +from typing import ( + Callable, + ClassVar, + Dict, + List, + Optional, + Tuple, + Type, + Union, + Any, + Set, +) import warnings from pydantic import ConfigDict, BaseModel, PrivateAttr @@ -24,7 +35,12 @@ from diffsync.diff import Diff from diffsync.enum import DiffSyncModelFlags, DiffSyncFlags, DiffSyncStatus -from diffsync.exceptions import DiffClassMismatch, ObjectAlreadyExists, ObjectStoreWrongType, ObjectNotFound +from diffsync.exceptions import ( + DiffClassMismatch, + ObjectAlreadyExists, + ObjectStoreWrongType, + ObjectNotFound, +) from diffsync.helpers import DiffSyncDiffer, DiffSyncSyncer from diffsync.store import BaseStore from diffsync.store.local import LocalStore @@ -97,15 +113,17 @@ class DiffSyncModel(BaseModel): Can be set as a class attribute or an instance attribute as needed. """ - diffsync: Optional["Adapter"] = None - """Optional: the DiffSync instance that owns this model instance.""" + adapter: Optional["Adapter"] = None + """Optional: the Adapter instance that owns this model instance.""" _status: DiffSyncStatus = PrivateAttr(DiffSyncStatus.SUCCESS) """Status of the last attempt at creating/updating/deleting this model.""" _status_message: str = PrivateAttr("") """Message, if any, associated with the create/update/delete status value.""" + model_config = ConfigDict(arbitrary_types_allowed=True) + """Pydantic-specific configuration to allow arbitrary types on this class.""" @classmethod def __pydantic_init_subclass__(cls, **kwargs: Any) -> None: @@ -145,15 +163,15 @@ def __str__(self) -> str: return self.get_unique_id() def dict(self, **kwargs: Any) -> Dict: - """Convert this DiffSyncModel to a dict, excluding the diffsync field by default as it is not serializable.""" + """Convert this DiffSyncModel to a dict, excluding the adapter field by default as it is not serializable.""" if "exclude" not in kwargs: - kwargs["exclude"] = {"diffsync"} + kwargs["exclude"] = {"adapter"} return super().model_dump(**kwargs) def json(self, **kwargs: Any) -> StrType: - """Convert this DiffSyncModel to a JSON string, excluding the diffsync field by default as it is not serializable.""" + """Convert this DiffSyncModel to a JSON string, excluding the adapter field by default as it is not serializable.""" if "exclude" not in kwargs: - kwargs["exclude"] = {"diffsync"} + kwargs["exclude"] = {"adapter"} if "exclude_defaults" not in kwargs: kwargs["exclude_defaults"] = True return super().model_dump_json(**kwargs) @@ -167,12 +185,12 @@ def str(self, include_children: bool = True, indent: int = 0) -> StrType: child_ids = getattr(self, fieldname) if not child_ids: output += ": []" - elif not self.diffsync or not include_children: + elif not self.adapter or not include_children: output += f": {child_ids}" else: for child_id in child_ids: try: - child = self.diffsync.get(modelname, child_id) + child = self.adapter.get(modelname, child_id) output += "\n" + child.str(include_children=include_children, indent=indent + 4) except ObjectNotFound: output += f"\n{margin} {child_id} (ERROR: details unavailable)" @@ -184,32 +202,32 @@ def set_status(self, status: DiffSyncStatus, message: StrType = "") -> None: self._status_message = message @classmethod - def create_base(cls, diffsync: "Adapter", ids: Dict, attrs: Dict) -> Optional[Self]: + def create_base(cls, adapter: "Adapter", ids: Dict, attrs: Dict) -> Optional[Self]: """Instantiate this class, along with any platform-specific data creation. This method is not meant to be subclassed, users should redefine create() instead. Args: - diffsync: The master data store for other DiffSyncModel instances that we might need to reference + adapter: The master data store for other DiffSyncModel instances that we might need to reference ids: Dictionary of unique-identifiers needed to create the new object attrs: Dictionary of additional attributes to set on the new object Returns: DiffSyncModel: instance of this class. """ - model = cls(**ids, diffsync=diffsync, **attrs) + model = cls(**ids, adapter=adapter, **attrs) model.set_status(DiffSyncStatus.SUCCESS, "Created successfully") return model @classmethod - def create(cls, diffsync: "Adapter", ids: Dict, attrs: Dict) -> Optional[Self]: + def create(cls, adapter: "Adapter", ids: Dict, attrs: Dict) -> Optional[Self]: """Instantiate this class, along with any platform-specific data creation. Subclasses must call `super().create()` or `self.create_base()`; they may wish to then override the default status information by calling `set_status()` to provide more context (such as details of any interactions with underlying systems). Args: - diffsync: The master data store for other DiffSyncModel instances that we might need to reference + adapter: The master data store for other DiffSyncModel instances that we might need to reference ids: Dictionary of unique-identifiers needed to create the new object attrs: Dictionary of additional attributes to set on the new object @@ -220,7 +238,7 @@ def create(cls, diffsync: "Adapter", ids: Dict, attrs: Dict) -> Optional[Self]: Raises: ObjectNotCreated: if an error occurred. """ - return cls.create_base(diffsync=diffsync, ids=ids, attrs=attrs) + return cls.create_base(adapter=adapter, ids=ids, attrs=attrs) def update_base(self, attrs: Dict) -> Optional[Self]: """Base Update method to update the attributes of this instance, along with any platform-specific data updates. @@ -376,7 +394,10 @@ def add_child(self, child: "DiffSyncModel") -> None: attr_name = self._children[child_type] childs = getattr(self, attr_name) if child.get_unique_id() in childs: - raise ObjectAlreadyExists(f"Already storing a {child_type} with unique_id {child.get_unique_id()}", child) + raise ObjectAlreadyExists( + f"Already storing a {child_type} with unique_id {child.get_unique_id()}", + child, + ) childs.append(child.get_unique_id()) def remove_child(self, child: "DiffSyncModel") -> None: @@ -417,7 +438,9 @@ class Adapter: # pylint: disable=too-many-public-methods """List of top-level modelnames to begin from when diffing or synchronizing.""" def __init__( - self, name: Optional[str] = None, internal_storage_engine: Union[Type[BaseStore], BaseStore] = LocalStore + self, + name: Optional[str] = None, + internal_storage_engine: Union[Type[BaseStore], BaseStore] = LocalStore, ) -> None: """Generic initialization function. @@ -426,9 +449,9 @@ def __init__( if isinstance(internal_storage_engine, BaseStore): self.store = internal_storage_engine - self.store.diffsync = self + self.store.adapter = self else: - self.store = internal_storage_engine(diffsync=self) + self.store = internal_storage_engine(adapter=self) # If the type is not defined, use the name of the class as the default value if self.type is None: @@ -565,7 +588,13 @@ def sync_from( # pylint: disable=too-many-arguments # Generate the diff if an existing diff was not provided if not diff: diff = self.diff_from(source, diff_class=diff_class, flags=flags, callback=callback) - syncer = DiffSyncSyncer(diff=diff, src_diffsync=source, dst_diffsync=self, flags=flags, callback=callback) + syncer = DiffSyncSyncer( + diff=diff, + src_diffsync=source, + dst_diffsync=self, + flags=flags, + callback=callback, + ) result = syncer.perform_sync() if result: self.sync_complete(source, diff, flags, syncer.base_logger) @@ -639,7 +668,11 @@ def diff_from( calculation of the diff proceeds. """ differ = DiffSyncDiffer( - src_diffsync=source, dst_diffsync=self, flags=flags, diff_class=diff_class, callback=callback + src_diffsync=source, + dst_diffsync=self, + flags=flags, + diff_class=diff_class, + callback=callback, ) return differ.calculate_diffs() @@ -674,7 +707,9 @@ def get_all_model_names(self) -> Set[StrType]: return self.store.get_all_model_names() def get( - self, obj: Union[StrType, DiffSyncModel, Type[DiffSyncModel]], identifier: Union[StrType, Dict] + self, + obj: Union[StrType, DiffSyncModel, Type[DiffSyncModel]], + identifier: Union[StrType, Dict], ) -> DiffSyncModel: """Get one object from the data store based on its unique id. @@ -689,7 +724,9 @@ def get( return self.store.get(model=obj, identifier=identifier) def get_or_none( - self, obj: Union[StrType, DiffSyncModel, Type[DiffSyncModel]], identifier: Union[StrType, Dict] + self, + obj: Union[StrType, DiffSyncModel, Type[DiffSyncModel]], + identifier: Union[StrType, Dict], ) -> Optional[DiffSyncModel]: """Get one object from the data store based on its unique id or get a None @@ -720,7 +757,9 @@ def get_all(self, obj: Union[StrType, DiffSyncModel, Type[DiffSyncModel]]) -> Li return self.store.get_all(model=obj) def get_by_uids( - self, uids: List[StrType], obj: Union[StrType, DiffSyncModel, Type[DiffSyncModel]] + self, + uids: List[StrType], + obj: Union[StrType, DiffSyncModel, Type[DiffSyncModel]], ) -> List[DiffSyncModel]: """Get multiple objects from the store by their unique IDs/Keys and type. diff --git a/diffsync/helpers.py b/diffsync/helpers.py index 6ed7bae..60c61f2 100644 --- a/diffsync/helpers.py +++ b/diffsync/helpers.py @@ -425,7 +425,7 @@ def sync_model( # pylint: disable=too-many-branches, unused-argument if self.action == DiffSyncActions.CREATE: if dst_model is not None: raise ObjectNotCreated(f"Failed to create {self.model_class.get_type()} {ids} - it already exists!") - dst_model = self.model_class.create(diffsync=self.dst_diffsync, ids=ids, attrs=attrs) + dst_model = self.model_class.create(adapter=self.dst_diffsync, ids=ids, attrs=attrs) elif self.action == DiffSyncActions.UPDATE: if dst_model is None: raise ObjectNotUpdated(f"Failed to update {self.model_class.get_type()} {ids} - not found!") diff --git a/diffsync/store/__init__.py b/diffsync/store/__init__.py index 3dacf33..0b18b76 100644 --- a/diffsync/store/__init__.py +++ b/diffsync/store/__init__.py @@ -15,12 +15,12 @@ class BaseStore: def __init__( self, # pylint: disable=unused-argument *args: Any, # pylint: disable=unused-argument - diffsync: Optional["Adapter"] = None, + adapter: Optional["Adapter"] = None, name: str = "", **kwargs: Any, # pylint: disable=unused-argument ) -> None: """Init method for BaseStore.""" - self.diffsync = diffsync + self.adapter = adapter self.name = name or self.__class__.__name__ self._log = structlog.get_logger().new(store=self) @@ -95,8 +95,8 @@ def remove(self, *, obj: "DiffSyncModel", remove_children: bool = False) -> None self.remove_item(modelname, uid) - if obj.diffsync: - obj.diffsync = None + if obj.adapter: + obj.adapter = None if remove_children: for child_type, child_fieldname in obj.get_children_mapping().items(): @@ -243,9 +243,9 @@ def _get_object_class_and_model( """Get object class and model name for a model.""" if isinstance(model, str): modelname = model - if not hasattr(self.diffsync, model): + if not hasattr(self.adapter, model): return None, modelname - object_class = getattr(self.diffsync, model) + object_class = getattr(self.adapter, model) else: object_class = model modelname = model.get_type() diff --git a/diffsync/store/local.py b/diffsync/store/local.py index 82bb69b..a56d1df 100644 --- a/diffsync/store/local.py +++ b/diffsync/store/local.py @@ -108,8 +108,8 @@ def add(self, *, obj: "DiffSyncModel") -> None: # Return so we don't have to change anything on the existing object and underlying data return - if not obj.diffsync: - obj.diffsync = self.diffsync + if not obj.adapter: + obj.adapter = self.adapter self._data[modelname][uid] = obj diff --git a/diffsync/store/redis.py b/diffsync/store/redis.py index 927dbf5..ab534dd 100644 --- a/diffsync/store/redis.py +++ b/diffsync/store/redis.py @@ -65,7 +65,7 @@ def _get_object_from_redis_key(self, key: str) -> "DiffSyncModel": pickled_object = self._store.get(key) if pickled_object: obj_result = loads(pickled_object) # nosec - obj_result.diffsync = self.diffsync + obj_result.adapter = self.adapter return obj_result raise ObjectNotFound(f"{key} not present in Cache") @@ -178,7 +178,7 @@ def add(self, *, obj: "DiffSyncModel") -> None: # Remove the diffsync object before sending to Redis obj_copy = copy.copy(obj) - obj_copy.diffsync = None + obj_copy.adapter = None self._store.set(object_key, dumps(obj_copy)) @@ -193,7 +193,7 @@ def update(self, *, obj: "DiffSyncModel") -> None: object_key = self._get_key_for_object(modelname, uid) obj_copy = copy.copy(obj) - obj_copy.diffsync = None + obj_copy.adapter = None self._store.set(object_key, dumps(obj_copy)) diff --git a/docs/source/getting_started/01-getting-started.md b/docs/source/getting_started/01-getting-started.md index 9b99cc3..3696dd7 100644 --- a/docs/source/getting_started/01-getting-started.md +++ b/docs/source/getting_started/01-getting-started.md @@ -181,10 +181,10 @@ class Device(DiffSyncModel): [...] @classmethod - def create(cls, diffsync, ids, attrs): + def create(cls, adapter, ids, attrs): ## TODO add your own logic here to create the device on the remote system # Call the super().create() method to create the in-memory DiffSyncModel instance - return super().create(ids=ids, diffsync=diffsync, attrs=attrs) + return super().create(ids=ids, adapter=adapter, attrs=attrs) def update(self, attrs): ## TODO add your own logic here to update the device on the remote system diff --git a/docs/source/upgrading/01-upgrading-to-2.0.md b/docs/source/upgrading/01-upgrading-to-2.0.md index 55e896b..34c7bfe 100644 --- a/docs/source/upgrading/01-upgrading-to-2.0.md +++ b/docs/source/upgrading/01-upgrading-to-2.0.md @@ -6,6 +6,8 @@ With diffsync 2.0, there a couple of breaking changes. What they are and how to The main diffsync class `diffsync.Diffsync` has been renamed to `diffsync.Adapter` as we have found that this is the verbiage that is most often used by users and explains the intent of the class clearer. The old name will still be around until 2.1, but is considered deprecated at this point. +As a consequence, a lot of fields have been renamed all across diffsync. To the end user, this will most prominently appear in the signature of the `create` method, where you will have to rename the `diffsync` parameter to `adapter`. + ## Upgrade to Pydantic's major version 2 A [migration guide](https://docs.pydantic.dev/latest/migration/) is available in the Pydantic documentation. Here are the key things that may apply to your usage of diffsync: diff --git a/examples/03-remote-system/nautobot_models.py b/examples/03-remote-system/nautobot_models.py index 2baba77..e2daf55 100644 --- a/examples/03-remote-system/nautobot_models.py +++ b/examples/03-remote-system/nautobot_models.py @@ -30,11 +30,11 @@ class NautobotCountry(Country): """Store the nautobot uuid in the object to allow update and delete of existing object.""" @classmethod - def create(cls, diffsync: Adapter, ids: dict, attrs: dict): + def create(cls, adapter: Adapter, ids: dict, attrs: dict): """Create a country object in Nautobot. Args: - diffsync: The master data store for other DiffSyncModel instances that we might need to reference + adapter: The master data store for other DiffSyncModel instances that we might need to reference ids: Dictionary of unique-identifiers needed to create the new object attrs: Dictionary of additional attributes to set on the new object @@ -43,11 +43,11 @@ def create(cls, diffsync: Adapter, ids: dict, attrs: dict): """ # Retrieve the parent region in internal cache to access its UUID # because the UUID is required to associate the object to its parent region in Nautobot - region = diffsync.get(diffsync.region, attrs.get("region")) + region = adapter.get(adapter.region, attrs.get("region")) # Create the new country in Nautobot and attach it to its parent try: - country = diffsync.nautobot.dcim.regions.create( + country = adapter.nautobot.dcim.regions.create( slug=ids.get("slug"), name=attrs.get("name"), custom_fields=dict(population=attrs.get("population")), @@ -61,7 +61,7 @@ def create(cls, diffsync: Adapter, ids: dict, attrs: dict): # Add the newly created remote_id and create the internal object for this resource. attrs["remote_id"] = country.id - item = super().create(ids=ids, diffsync=diffsync, attrs=attrs) + item = super().create(ids=ids, adapter=adapter, attrs=attrs) return item def update(self, attrs: dict): @@ -78,7 +78,7 @@ def update(self, attrs: dict): ObjectNotUpdated: if an error occurred. """ # Retrive the pynautobot object from Nautobot since we only have the UUID internally - remote = self.diffsync.nautobot.dcim.regions.get(self.remote_id) + remote = self.adapter.nautobot.dcim.regions.get(self.remote_id) # Convert the internal attrs to Nautobot format if "population" in attrs: @@ -98,7 +98,7 @@ def delete(self): NautobotCountry: DiffSync object """ # Retrieve the pynautobot object and delete the object in Nautobot - remote = self.diffsync.nautobot.dcim.regions.get(self.remote_id) + remote = self.adapter.nautobot.dcim.regions.get(self.remote_id) remote.delete() super().delete() diff --git a/examples/05-nautobot-peeringdb/adapter_nautobot.py b/examples/05-nautobot-peeringdb/adapter_nautobot.py index 84be2d6..51b6f8a 100644 --- a/examples/05-nautobot-peeringdb/adapter_nautobot.py +++ b/examples/05-nautobot-peeringdb/adapter_nautobot.py @@ -9,11 +9,11 @@ class RegionNautobotModel(RegionModel): """Implementation of Region create/update/delete methods for updating remote Nautobot data.""" @classmethod - def create(cls, diffsync, ids, attrs): + def create(cls, adapter, ids, attrs): """Create a new Region record in remote Nautobot. Args: - diffsync (NautobotRemote): DiffSync adapter owning this Region + adapter (NautobotRemote): DiffSync adapter owning this Region ids (dict): Initial values for this model's _identifiers attrs (dict): Initial values for this model's _attributes """ @@ -24,11 +24,11 @@ def create(cls, diffsync, ids, attrs): if attrs["description"]: data["description"] = attrs["description"] if attrs["parent_name"]: - data["parent"] = str(diffsync.get(diffsync.region, attrs["parent_name"]).pk) + data["parent"] = str(adapter.get(adapter.region, attrs["parent_name"]).pk) - diffsync.nautobot_api.dcim.regions.create(**data) + adapter.nautobot_api.dcim.regions.create(**data) - return super().create(diffsync, ids=ids, attrs=attrs) + return super().create(adapter, ids=ids, attrs=attrs) def update(self, attrs): """Update an existing Region record in remote Nautobot. @@ -36,7 +36,7 @@ def update(self, attrs): Args: attrs (dict): Updated values for this record's _attributes """ - region = self.diffsync.nautobot_api.dcim.regions.get(name=self.name) + region = self.adapter.nautobot_api.dcim.regions.get(name=self.name) data = {} if "slug" in attrs: data["slug"] = attrs["slug"] @@ -44,7 +44,7 @@ def update(self, attrs): data["description"] = attrs["description"] if "parent_name" in attrs: if attrs["parent_name"]: - data["parent"] = str(self.diffsync.get(self.diffsync.region, attrs["parent_name"]).name) + data["parent"] = str(self.adapter.get(self.adapter.region, attrs["parent_name"]).name) else: data["parent"] = None @@ -62,15 +62,15 @@ class SiteNautobotModel(SiteModel): """Implementation of Site create/update/delete methods for updating remote Nautobot data.""" @classmethod - def create(cls, diffsync, ids, attrs): + def create(cls, adapter, ids, attrs): """Create a new Site in remote Nautobot. Args: - diffsync (NautobotRemote): DiffSync adapter owning this Site + adapter (NautobotRemote): DiffSync adapter owning this Site ids (dict): Initial values for this model's _identifiers attrs (dict): Initial values for this model's _attributes """ - diffsync.nautobot_api.dcim.sites.create( + adapter.nautobot_api.dcim.sites.create( name=ids["name"], slug=attrs["slug"], description=attrs["description"], @@ -79,7 +79,7 @@ def create(cls, diffsync, ids, attrs): latitude=attrs["latitude"], longitude=attrs["longitude"], ) - return super().create(diffsync, ids=ids, attrs=attrs) + return super().create(adapter, ids=ids, attrs=attrs) def update(self, attrs): """Update an existing Site record in remote Nautobot. @@ -87,7 +87,7 @@ def update(self, attrs): Args: attrs (dict): Updated values for this record's _attributes """ - site = self.diffsync.nautobot_api.dcim.sites.get(name=self.name) + site = self.adapter.nautobot_api.dcim.sites.get(name=self.name) data = {} if "slug" in attrs: diff --git a/examples/06-ip-prefixes/adapter_ipam_a.py b/examples/06-ip-prefixes/adapter_ipam_a.py index 5463cd4..0350878 100644 --- a/examples/06-ip-prefixes/adapter_ipam_a.py +++ b/examples/06-ip-prefixes/adapter_ipam_a.py @@ -12,9 +12,9 @@ class IpamAPrefix(Prefix): """Implementation of Prefix create/update/delete methods for IPAM A.""" @classmethod - def create(cls, diffsync, ids, attrs): + def create(cls, adapter, ids, attrs): """Create a Prefix record in IPAM A.""" - diffsync.data.append( + adapter.data.append( { "cidr": ids["prefix"], "family": ipaddress.ip_address(ids["prefix"].split("/")[0]).version, @@ -24,11 +24,11 @@ def create(cls, diffsync, ids, attrs): } ) - return super().create(diffsync, ids=ids, attrs=attrs) + return super().create(adapter, ids=ids, attrs=attrs) def update(self, attrs): """Update a Prefix record in IPAM A.""" - for elem in self.diffsync.data: + for elem in self.adapter.data: if elem["cidr"] == self.prefix: if "vrf" in attrs: elem["vrf"] = attrs["vrf"] @@ -42,9 +42,9 @@ def update(self, attrs): def delete(self): """Delete a Prefix record in IPAM A.""" - for index, elem in enumerate(self.diffsync.data): + for index, elem in enumerate(self.adapter.data): if elem["cidr"] == self.prefix: - del self.diffsync.data[index] + del self.adapter.data[index] break return super().delete() diff --git a/examples/06-ip-prefixes/adapter_ipam_b.py b/examples/06-ip-prefixes/adapter_ipam_b.py index 7a6eadb..4e66e19 100644 --- a/examples/06-ip-prefixes/adapter_ipam_b.py +++ b/examples/06-ip-prefixes/adapter_ipam_b.py @@ -11,9 +11,9 @@ class IpamBPrefix(Prefix): """Implementation of Prefix create/update/delete methods for IPAM B.""" @classmethod - def create(cls, diffsync, ids, attrs): + def create(cls, adapter, ids, attrs): """Create a Prefix record in IPAM B.""" - diffsync.data.append( + adapter.data.append( { "network": ids["prefix"].split("/")[0], "prefix_length": int(ids["prefix"].split("/")[1]), @@ -23,14 +23,14 @@ def create(cls, diffsync, ids, attrs): } ) - return super().create(diffsync, ids=ids, attrs=attrs) + return super().create(adapter, ids=ids, attrs=attrs) def update(self, attrs): """Update a Prefix record in IPAM B.""" network = self.prefix.split("/")[0] prefix_length = int(self.prefix.split("/")[1]) - for elem in self.diffsync.data: + for elem in self.adapter.data: if elem["network"] == network and elem["prefix_length"] == prefix_length: if "vrf" in attrs: elem["vrf"] = attrs["vrf"] @@ -47,9 +47,9 @@ def delete(self): network = self.prefix.split("/")[0] prefix_length = int(self.prefix.split("/")[1]) - for index, elem in enumerate(self.diffsync.data): + for index, elem in enumerate(self.adapter.data): if elem["network"] == network and elem["prefix_length"] == prefix_length: - del self.diffsync.data[index] + del self.adapter.data[index] break return super().delete() diff --git a/tests/unit/conftest.py b/tests/unit/conftest.py index c72d213..bfe096b 100644 --- a/tests/unit/conftest.py +++ b/tests/unit/conftest.py @@ -35,14 +35,14 @@ class ErrorProneModelMixin: _counter: ClassVar[int] = 0 @classmethod - def create(cls, diffsync: Adapter, ids: Dict, attrs: Dict): + def create(cls, adapter: Adapter, ids: Dict, attrs: Dict): """As DiffSyncModel.create(), but periodically throw exceptions.""" cls._counter += 1 if not cls._counter % 5: raise ObjectNotCreated("Random creation error!") if not cls._counter % 4: return None # non-fatal error - return super().create(diffsync, ids, attrs) # type: ignore + return super().create(adapter, ids, attrs) # type: ignore def update(self, attrs: Dict): """As DiffSyncModel.update(), but periodically throw exceptions.""" @@ -69,7 +69,7 @@ class ExceptionModelMixin: """Test class that always throws exceptions when creating/updating/deleting instances.""" @classmethod - def create(cls, diffsync: Adapter, ids: Dict, attrs: Dict): + def create(cls, adapter: Adapter, ids: Dict, attrs: Dict): """As DiffSyncModel.create(), but always throw exceptions.""" raise NotImplementedError @@ -158,8 +158,8 @@ def interface(device_name="device1", name="eth0", **kwargs): @pytest.fixture -def generic_diffsync(): - """Provide a generic DiffSync instance.""" +def generic_adapter(): + """Provide a generic Adapter instance.""" return Adapter() @@ -173,7 +173,7 @@ class UnusedModel(DiffSyncModel): class GenericBackend(Adapter): - """An example semi-abstract subclass of DiffSync.""" + """An example semi-abstract subclass of Adapter.""" site = Site # to be overridden by subclasses device = Device diff --git a/tests/unit/test_diffsync.py b/tests/unit/test_diffsync.py index 6f7feae..088bd4b 100644 --- a/tests/unit/test_diffsync.py +++ b/tests/unit/test_diffsync.py @@ -1,4 +1,4 @@ -"""Unit tests for the DiffSync class.""" +"""Unit tests for the Adapter class.""" # pylint: disable=too-many-lines from unittest import mock @@ -12,339 +12,339 @@ from .conftest import Site, Device, Interface, TrackedDiff, BackendA, PersonA -def test_diffsync_default_name_type(generic_diffsync): - assert generic_diffsync.type == "Adapter" - assert generic_diffsync.name == "Adapter" +def test_diffsync_default_name_type(generic_adapter): + assert generic_adapter.type == "Adapter" + assert generic_adapter.name == "Adapter" -def test_diffsync_generic_load_is_noop(generic_diffsync): - generic_diffsync.load() - assert generic_diffsync.count() == 0 +def test_diffsync_generic_load_is_noop(generic_adapter): + generic_adapter.load() + assert generic_adapter.count() == 0 -def test_diffsync_dict_with_no_data(generic_diffsync): - assert generic_diffsync.dict() == {} +def test_diffsync_dict_with_no_data(generic_adapter): + assert generic_adapter.dict() == {} -def test_diffsync_str_with_no_data(generic_diffsync): - assert generic_diffsync.str() == "" +def test_diffsync_str_with_no_data(generic_adapter): + assert generic_adapter.str() == "" -def test_diffsync_len_with_no_data(generic_diffsync): - assert len(generic_diffsync) == 0 +def test_diffsync_len_with_no_data(generic_adapter): + assert len(generic_adapter) == 0 -def test_diffsync_diff_self_with_no_data_has_no_diffs(generic_diffsync): - assert generic_diffsync.diff_from(generic_diffsync).has_diffs() is False - assert generic_diffsync.diff_to(generic_diffsync).has_diffs() is False +def test_diffsync_diff_self_with_no_data_has_no_diffs(generic_adapter): + assert generic_adapter.diff_from(generic_adapter).has_diffs() is False + assert generic_adapter.diff_to(generic_adapter).has_diffs() is False -def test_diffsync_sync_self_with_no_data_is_noop(generic_diffsync): - generic_diffsync.sync_complete = mock.Mock() - generic_diffsync.sync_from(generic_diffsync) - diff = generic_diffsync.sync_to(generic_diffsync) +def test_diffsync_sync_self_with_no_data_is_noop(generic_adapter): + generic_adapter.sync_complete = mock.Mock() + generic_adapter.sync_from(generic_adapter) + diff = generic_adapter.sync_to(generic_adapter) # Check if the returning Diff object has diffs assert not diff.has_diffs() # sync_complete() should only be called if something actually changed - assert not generic_diffsync.sync_complete.called + assert not generic_adapter.sync_complete.called -def test_diffsync_get_with_no_data_fails(generic_diffsync): +def test_diffsync_get_with_no_data_fails(generic_adapter): with pytest.raises(ObjectNotFound): - generic_diffsync.get("anything", "myname") + generic_adapter.get("anything", "myname") with pytest.raises(ObjectNotFound): - generic_diffsync.get(DiffSyncModel, "") + generic_adapter.get(DiffSyncModel, "") -def test_diffsync_get_all_with_no_data_is_empty_list(generic_diffsync): - assert not list(generic_diffsync.get_all("anything")) - assert not list(generic_diffsync.get_all(DiffSyncModel)) +def test_diffsync_get_all_with_no_data_is_empty_list(generic_adapter): + assert not list(generic_adapter.get_all("anything")) + assert not list(generic_adapter.get_all(DiffSyncModel)) -def test_diffsync_get_by_uids_with_no_data(generic_diffsync): - assert not generic_diffsync.get_by_uids([], "anything") - assert not generic_diffsync.get_by_uids([], DiffSyncModel) +def test_diffsync_get_by_uids_with_no_data(generic_adapter): + assert not generic_adapter.get_by_uids([], "anything") + assert not generic_adapter.get_by_uids([], DiffSyncModel) with pytest.raises(ObjectNotFound): - generic_diffsync.get_by_uids(["any", "another"], "anything") + generic_adapter.get_by_uids(["any", "another"], "anything") with pytest.raises(ObjectNotFound): - generic_diffsync.get_by_uids(["any", "another"], DiffSyncModel) + generic_adapter.get_by_uids(["any", "another"], DiffSyncModel) -def test_diffsync_add_no_raises_existing_same_object(generic_diffsync): +def test_diffsync_add_no_raises_existing_same_object(generic_adapter): person = PersonA(name="Mikhail Yohman") modelname = person.get_type() uid = person.get_unique_id() # First attempt at adding object - generic_diffsync.add(person) - assert modelname in generic_diffsync.get_all_model_names() - assert any(uid == obj.get_unique_id() for obj in generic_diffsync.get_all(modelname)) + generic_adapter.add(person) + assert modelname in generic_adapter.get_all_model_names() + assert any(uid == obj.get_unique_id() for obj in generic_adapter.get_all(modelname)) - assert person == generic_diffsync.get(modelname, uid) + assert person == generic_adapter.get(modelname, uid) # Attempt to add again and make sure it doesn't raise an exception - generic_diffsync.add(person) - assert person is generic_diffsync.get(modelname, uid) - assert person is generic_diffsync.get(PersonA, "Mikhail Yohman") + generic_adapter.add(person) + assert person is generic_adapter.get(modelname, uid) + assert person is generic_adapter.get(PersonA, "Mikhail Yohman") -def test_diffsync_add_raises_already_exists_with_updated_object(generic_diffsync): +def test_diffsync_add_raises_already_exists_with_updated_object(generic_adapter): intf = Interface(device_name="device1", name="eth0") # A DiffSync can store arbitrary DiffSyncModel objects, even if it doesn't know about them at definition time. - generic_diffsync.add(intf) + generic_adapter.add(intf) # Create new interface with same identifiers so it's technically the same object, but set additional attribute new_intf = Interface(device_name="device1", name="eth0", interface_type="1000base-t") with pytest.raises(ObjectAlreadyExists) as error: - generic_diffsync.add(new_intf) + generic_adapter.add(new_intf) error_model = error.value.existing_object assert isinstance(error_model, DiffSyncModel) assert new_intf is error_model -def test_diffsync_get_or_instantiate_create_non_existent_object(generic_diffsync): - generic_diffsync.interface = Interface +def test_diffsync_get_or_instantiate_create_non_existent_object(generic_adapter): + generic_adapter.interface = Interface intf_identifiers = {"device_name": "device1", "name": "eth1"} # Assert that the object does not currently exist. with pytest.raises(ObjectNotFound): - generic_diffsync.get(Interface, intf_identifiers) + generic_adapter.get(Interface, intf_identifiers) - obj, created = generic_diffsync.get_or_instantiate(Interface, intf_identifiers) + obj, created = generic_adapter.get_or_instantiate(Interface, intf_identifiers) assert created - assert obj is generic_diffsync.get(Interface, intf_identifiers) - assert obj is generic_diffsync.get("interface", intf_identifiers) + assert obj is generic_adapter.get(Interface, intf_identifiers) + assert obj is generic_adapter.get("interface", intf_identifiers) -def test_diffsync_get_or_instantiate_retrieve_existing_object(generic_diffsync): +def test_diffsync_get_or_instantiate_retrieve_existing_object(generic_adapter): intf_identifiers = {"device_name": "device1", "name": "eth1"} intf = Interface(**intf_identifiers) - generic_diffsync.add(intf) + generic_adapter.add(intf) - obj, created = generic_diffsync.get_or_instantiate(Interface, intf_identifiers) + obj, created = generic_adapter.get_or_instantiate(Interface, intf_identifiers) assert obj is intf assert not created -def test_diffsync_get_or_instantiate_retrieve_existing_object_w_attrs(generic_diffsync): +def test_diffsync_get_or_instantiate_retrieve_existing_object_w_attrs(generic_adapter): intf_identifiers = {"device_name": "device1", "name": "eth1"} intf_attrs = {"interface_type": "1000base-t", "description": "Testing"} intf = Interface(**intf_identifiers) - generic_diffsync.add(intf) + generic_adapter.add(intf) - obj, created = generic_diffsync.get_or_instantiate(Interface, intf_identifiers, intf_attrs) + obj, created = generic_adapter.get_or_instantiate(Interface, intf_identifiers, intf_attrs) assert obj is intf assert not created assert obj.interface_type == "ethernet" assert obj.description is None -def test_diffsync_get_or_instantiate_retrieve_create_non_existent_w_attrs(generic_diffsync): - generic_diffsync.interface = Interface +def test_diffsync_get_or_instantiate_retrieve_create_non_existent_w_attrs(generic_adapter): + generic_adapter.interface = Interface intf_identifiers = {"device_name": "device1", "name": "eth1"} intf_attrs = {"interface_type": "1000base-t", "description": "Testing"} - obj, created = generic_diffsync.get_or_instantiate(Interface, intf_identifiers, intf_attrs) + obj, created = generic_adapter.get_or_instantiate(Interface, intf_identifiers, intf_attrs) assert created assert obj.interface_type == "1000base-t" assert obj.description == "Testing" - assert obj is generic_diffsync.get(Interface, intf_identifiers) - assert obj is generic_diffsync.get("interface", intf_identifiers) + assert obj is generic_adapter.get(Interface, intf_identifiers) + assert obj is generic_adapter.get("interface", intf_identifiers) -def test_diffsync_get_or_instantiate_retrieve_existing_object_wo_attrs(generic_diffsync): +def test_diffsync_get_or_instantiate_retrieve_existing_object_wo_attrs(generic_adapter): intf_identifiers = {"device_name": "device1", "name": "eth1"} intf = Interface(**intf_identifiers) - generic_diffsync.add(intf) + generic_adapter.add(intf) - obj, created = generic_diffsync.get_or_instantiate(Interface, intf_identifiers) + obj, created = generic_adapter.get_or_instantiate(Interface, intf_identifiers) assert obj is intf assert not created assert obj.interface_type == "ethernet" assert obj.description is None -def test_diffsync_get_or_add_model_instance_create_non_existent_object(generic_diffsync): - generic_diffsync.interface = Interface +def test_diffsync_get_or_add_model_instance_create_non_existent_object(generic_adapter): + generic_adapter.interface = Interface intf_identifiers = {"device_name": "device1", "name": "eth1"} - intf = generic_diffsync.interface(**intf_identifiers) + intf = generic_adapter.interface(**intf_identifiers) # Assert that the object does not currently exist. with pytest.raises(ObjectNotFound): - generic_diffsync.get(Interface, intf_identifiers) + generic_adapter.get(Interface, intf_identifiers) - obj, created = generic_diffsync.get_or_add_model_instance(intf) + obj, created = generic_adapter.get_or_add_model_instance(intf) assert created - assert obj is generic_diffsync.get(Interface, intf_identifiers) - assert obj is generic_diffsync.get("interface", intf_identifiers) + assert obj is generic_adapter.get(Interface, intf_identifiers) + assert obj is generic_adapter.get("interface", intf_identifiers) -def test_diffsync_get_or_add_model_instance_retrieve_existing_object(generic_diffsync): +def test_diffsync_get_or_add_model_instance_retrieve_existing_object(generic_adapter): intf_identifiers = {"device_name": "device1", "name": "eth1"} intf = Interface(**intf_identifiers) - generic_diffsync.add(intf) + generic_adapter.add(intf) - obj, created = generic_diffsync.get_or_add_model_instance(intf) + obj, created = generic_adapter.get_or_add_model_instance(intf) assert obj is intf assert not created -def test_diffsync_get_or_add_model_instance_retrieve_existing_object_w_attrs(generic_diffsync): +def test_diffsync_get_or_add_model_instance_retrieve_existing_object_w_attrs(generic_adapter): intf_identifiers = {"device_name": "device1", "name": "eth1"} intf_attrs = {"interface_type": "ethernet"} intf_combine = {**intf_identifiers, **intf_attrs} intf = Interface(**intf_combine) - generic_diffsync.add(intf) + generic_adapter.add(intf) - obj, created = generic_diffsync.get_or_add_model_instance(intf) + obj, created = generic_adapter.get_or_add_model_instance(intf) assert obj is intf assert not created assert obj.interface_type == "ethernet" assert obj.description is None -def test_diffsync_get_or_add_model_instance_retrieve_create_non_existent_w_attrs(generic_diffsync): - generic_diffsync.interface = Interface +def test_diffsync_get_or_add_model_instance_retrieve_create_non_existent_w_attrs(generic_adapter): + generic_adapter.interface = Interface intf_identifiers = {"device_name": "device1", "name": "eth1"} intf_attrs = {"interface_type": "1000base-t", "description": "Testing"} intf_combine = {**intf_identifiers, **intf_attrs} intf = Interface(**intf_combine) - obj, created = generic_diffsync.get_or_add_model_instance(intf) + obj, created = generic_adapter.get_or_add_model_instance(intf) assert created assert obj.interface_type == "1000base-t" assert obj.description == "Testing" - assert obj is generic_diffsync.get(Interface, intf_identifiers) - assert obj is generic_diffsync.get("interface", intf_identifiers) + assert obj is generic_adapter.get(Interface, intf_identifiers) + assert obj is generic_adapter.get("interface", intf_identifiers) -def test_diffsync_get_or_add_model_instance_retrieve_existing_object_wo_attrs(generic_diffsync): +def test_diffsync_get_or_add_model_instance_retrieve_existing_object_wo_attrs(generic_adapter): intf_identifiers = {"device_name": "device1", "name": "eth1"} intf = Interface(**intf_identifiers) - generic_diffsync.add(intf) + generic_adapter.add(intf) - obj, created = generic_diffsync.get_or_add_model_instance(intf) + obj, created = generic_adapter.get_or_add_model_instance(intf) assert obj is intf assert not created assert obj.interface_type == "ethernet" assert obj.description is None -def test_diffsync_update_or_instantiate_retrieve_existing_object_w_updated_attrs(generic_diffsync): +def test_diffsync_update_or_instantiate_retrieve_existing_object_w_updated_attrs(generic_adapter): intf_identifiers = {"device_name": "device1", "name": "eth1"} intf_attrs = {"interface_type": "1000base-t", "description": "Testing"} intf = Interface(**intf_identifiers) - generic_diffsync.add(intf) + generic_adapter.add(intf) - obj, created = generic_diffsync.update_or_instantiate(Interface, intf_identifiers, intf_attrs) + obj, created = generic_adapter.update_or_instantiate(Interface, intf_identifiers, intf_attrs) assert obj is intf assert not created assert obj.interface_type == "1000base-t" assert obj.description == "Testing" -def test_diffsync_update_or_instantiate_create_object(generic_diffsync): +def test_diffsync_update_or_instantiate_create_object(generic_adapter): intf_identifiers = {"device_name": "device1", "name": "eth1"} - obj, created = generic_diffsync.update_or_instantiate(Interface, intf_identifiers, {}) + obj, created = generic_adapter.update_or_instantiate(Interface, intf_identifiers, {}) assert created assert obj.interface_type == "ethernet" assert obj.description is None -def test_diffsync_update_or_instantiate_create_object_w_attrs(generic_diffsync): +def test_diffsync_update_or_instantiate_create_object_w_attrs(generic_adapter): intf_identifiers = {"device_name": "device1", "name": "eth1"} intf_attrs = {"interface_type": "1000base-t", "description": "Testing"} - obj, created = generic_diffsync.update_or_instantiate(Interface, intf_identifiers, intf_attrs) + obj, created = generic_adapter.update_or_instantiate(Interface, intf_identifiers, intf_attrs) assert created assert obj.interface_type == "1000base-t" assert obj.description == "Testing" -def test_diffsync_update_or_add_model_instance_retrieve_existing_object_w_updated_attrs(generic_diffsync): +def test_diffsync_update_or_add_model_instance_retrieve_existing_object_w_updated_attrs(generic_adapter): intf_identifiers = {"device_name": "device1", "name": "eth1"} intf_attrs = {"interface_type": "1000base-t", "description": "Testing"} intf_combine = {**intf_identifiers, **intf_attrs} intf = Interface(**intf_combine) - generic_diffsync.add(intf) + generic_adapter.add(intf) - obj, created = generic_diffsync.update_or_add_model_instance(intf) + obj, created = generic_adapter.update_or_add_model_instance(intf) assert obj is intf assert not created assert obj.interface_type == "1000base-t" assert obj.description == "Testing" -def test_diffsync_update_or_add_model_instance_create_object(generic_diffsync): +def test_diffsync_update_or_add_model_instance_create_object(generic_adapter): intf_identifiers = {"device_name": "device1", "name": "eth1"} intf = Interface(**intf_identifiers) - obj, created = generic_diffsync.update_or_add_model_instance(intf) + obj, created = generic_adapter.update_or_add_model_instance(intf) assert created assert obj.interface_type == "ethernet" assert obj.description is None -def test_diffsync_update_or_add_model_instance_create_object_w_attrs(generic_diffsync): +def test_diffsync_update_or_add_model_instance_create_object_w_attrs(generic_adapter): intf_identifiers = {"device_name": "device1", "name": "eth1"} intf_attrs = {"interface_type": "1000base-t", "description": "Testing"} intf_combine = {**intf_identifiers, **intf_attrs} intf = Interface(**intf_combine) - obj, created = generic_diffsync.update_or_add_model_instance(intf) + obj, created = generic_adapter.update_or_add_model_instance(intf) assert created assert obj.interface_type == "1000base-t" assert obj.description == "Testing" -def test_diffsync_get_with_generic_model(generic_diffsync, generic_diffsync_model): - generic_diffsync.add(generic_diffsync_model) +def test_diffsync_get_with_generic_model(generic_adapter, generic_diffsync_model): + generic_adapter.add(generic_diffsync_model) # The generic_diffsync_model has an empty identifier/unique-id - assert generic_diffsync.get(DiffSyncModel, "") == generic_diffsync_model - assert generic_diffsync.get(DiffSyncModel.get_type(), "") == generic_diffsync_model + assert generic_adapter.get(DiffSyncModel, "") == generic_diffsync_model + assert generic_adapter.get(DiffSyncModel.get_type(), "") == generic_diffsync_model # DiffSync doesn't know how to construct a uid str for a "diffsyncmodel" (it needs the class or instance, not a str) with pytest.raises(ValueError): - generic_diffsync.get(DiffSyncModel.get_type(), {}) + generic_adapter.get(DiffSyncModel.get_type(), {}) # Wrong object-type - no match with pytest.raises(ObjectNotFound): - generic_diffsync.get("", "") + generic_adapter.get("", "") # Wrong unique-id - no match with pytest.raises(ObjectNotFound): - generic_diffsync.get(DiffSyncModel, "myname") + generic_adapter.get(DiffSyncModel, "myname") -def test_diffsync_get_all_with_generic_model(generic_diffsync, generic_diffsync_model): - generic_diffsync.add(generic_diffsync_model) - assert list(generic_diffsync.get_all(DiffSyncModel)) == [generic_diffsync_model] - assert list(generic_diffsync.get_all(DiffSyncModel.get_type())) == [generic_diffsync_model] +def test_diffsync_get_all_with_generic_model(generic_adapter, generic_diffsync_model): + generic_adapter.add(generic_diffsync_model) + assert list(generic_adapter.get_all(DiffSyncModel)) == [generic_diffsync_model] + assert list(generic_adapter.get_all(DiffSyncModel.get_type())) == [generic_diffsync_model] # Wrong object-type - no match - assert not list(generic_diffsync.get_all("anything")) + assert not list(generic_adapter.get_all("anything")) -def test_diffsync_get_by_uids_with_generic_model(generic_diffsync, generic_diffsync_model): - generic_diffsync.add(generic_diffsync_model) - assert generic_diffsync.get_by_uids([""], DiffSyncModel) == [generic_diffsync_model] - assert generic_diffsync.get_by_uids([""], DiffSyncModel.get_type()) == [generic_diffsync_model] +def test_diffsync_get_by_uids_with_generic_model(generic_adapter, generic_diffsync_model): + generic_adapter.add(generic_diffsync_model) + assert generic_adapter.get_by_uids([""], DiffSyncModel) == [generic_diffsync_model] + assert generic_adapter.get_by_uids([""], DiffSyncModel.get_type()) == [generic_diffsync_model] # Wrong unique-id - no match with pytest.raises(ObjectNotFound): - generic_diffsync.get_by_uids(["myname"], DiffSyncModel) + generic_adapter.get_by_uids(["myname"], DiffSyncModel) # Valid unique-id mixed in with unknown ones with pytest.raises(ObjectNotFound): - generic_diffsync.get_by_uids(["aname", "", "anothername"], DiffSyncModel) + generic_adapter.get_by_uids(["aname", "", "anothername"], DiffSyncModel) -def test_diffsync_remove_with_generic_model(generic_diffsync, generic_diffsync_model): - generic_diffsync.add(generic_diffsync_model) - generic_diffsync.remove(generic_diffsync_model) +def test_diffsync_remove_with_generic_model(generic_adapter, generic_diffsync_model): + generic_adapter.add(generic_diffsync_model) + generic_adapter.remove(generic_diffsync_model) with pytest.raises(ObjectNotFound): - generic_diffsync.remove(generic_diffsync_model) + generic_adapter.remove(generic_diffsync_model) with pytest.raises(ObjectNotFound): - generic_diffsync.get(DiffSyncModel, "") - assert not list(generic_diffsync.get_all(DiffSyncModel)) + generic_adapter.get(DiffSyncModel, "") + assert not list(generic_adapter.get_all(DiffSyncModel)) with pytest.raises(ObjectNotFound): - generic_diffsync.get_by_uids([""], DiffSyncModel) + generic_adapter.get_by_uids([""], DiffSyncModel) def test_diffsync_subclass_validation_name_mismatch(): diff --git a/tests/unit/test_diffsync_model.py b/tests/unit/test_diffsync_model.py index e7d6a6b..0adedb2 100644 --- a/tests/unit/test_diffsync_model.py +++ b/tests/unit/test_diffsync_model.py @@ -159,41 +159,41 @@ def test_diffsync_model_subclass_add_remove(make_site, make_device, make_interfa device1.remove_child(device1_eth0) -def test_diffsync_model_dict_with_children(generic_diffsync, make_site, make_device, make_interface): - site1 = make_site(diffsync=generic_diffsync) - device1 = make_device(diffsync=generic_diffsync) - device1_eth0 = make_interface(diffsync=generic_diffsync) +def test_diffsync_model_dict_with_children(generic_adapter, make_site, make_device, make_interface): + site1 = make_site(diffsync=generic_adapter) + device1 = make_device(diffsync=generic_adapter) + device1_eth0 = make_interface(diffsync=generic_adapter) site1.add_child(device1) device1.add_child(device1_eth0) # test error handling - diffsync knows about site and device but not interface - generic_diffsync.add(site1) - generic_diffsync.add(device1) + generic_adapter.add(site1) + generic_adapter.add(device1) assert site1.dict() == {"devices": ["device1"], "model_flags": DiffSyncModelFlags.NONE, "name": "site1"} -def test_diffsync_model_json_with_children(generic_diffsync, make_site, make_device, make_interface): - site1 = make_site(diffsync=generic_diffsync) - device1 = make_device(diffsync=generic_diffsync) - device1_eth0 = make_interface(diffsync=generic_diffsync) +def test_diffsync_model_json_with_children(generic_adapter, make_site, make_device, make_interface): + site1 = make_site(diffsync=generic_adapter) + device1 = make_device(diffsync=generic_adapter) + device1_eth0 = make_interface(diffsync=generic_adapter) site1.add_child(device1) device1.add_child(device1_eth0) # test error handling - diffsync knows about site and device but not interface - generic_diffsync.add(site1) - generic_diffsync.add(device1) + generic_adapter.add(site1) + generic_adapter.add(device1) assert site1.json() == '{"name":"site1","devices":["device1"]}' -def test_diffsync_model_str_with_children(generic_diffsync, make_site, make_device, make_interface): - site1 = make_site(diffsync=generic_diffsync) - device1 = make_device(diffsync=generic_diffsync) - device1_eth0 = make_interface(diffsync=generic_diffsync) +def test_diffsync_model_str_with_children(generic_adapter, make_site, make_device, make_interface): + site1 = make_site(diffsync=generic_adapter) + device1 = make_device(diffsync=generic_adapter) + device1_eth0 = make_interface(diffsync=generic_adapter) site1.add_child(device1) device1.add_child(device1_eth0) # test error handling - diffsync knows about site and device but not interface - generic_diffsync.add(site1) - generic_diffsync.add(device1) + generic_adapter.add(site1) + generic_adapter.add(device1) assert ( site1.str() @@ -215,21 +215,21 @@ def test_diffsync_model_str_with_children(generic_diffsync, make_site, make_devi ) -def test_diffsync_model_subclass_crud(generic_diffsync): +def test_diffsync_model_subclass_crud(generic_adapter): """Test basic CRUD operations on generic DiffSyncModel subclasses.""" - device1 = Device.create(generic_diffsync, {"name": "device1"}, {"role": "spine"}) + device1 = Device.create(generic_adapter, {"name": "device1"}, {"role": "spine"}) assert isinstance(device1, Device) - assert device1.diffsync == generic_diffsync + assert device1.adapter == generic_adapter assert device1.name == "device1" assert device1.role == "spine" device1_eth0 = Interface.create( - generic_diffsync, + generic_adapter, {"name": "eth0", "device_name": "device1"}, {"description": "some description"}, ) assert isinstance(device1_eth0, Interface) - assert device1_eth0.diffsync == generic_diffsync + assert device1_eth0.adapter == generic_adapter assert device1_eth0.name == "eth0" assert device1_eth0.device_name == "device1" assert device1_eth0.description == "some description"