From 507ba8fa216bd53c435d56e0b108c89ee86f570d Mon Sep 17 00:00:00 2001 From: Joe Wesch Date: Wed, 11 Mar 2026 10:50:37 -0500 Subject: [PATCH 1/3] Bump version --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 34267ec..88e5600 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "diffsync" -version = "2.2.2" +version = "2.2.3a0" description = "Library to easily sync/diff/update 2 different data sources" authors = ["Network to Code, LLC "] license = "Apache-2.0" From 5a727509f791c01aac4ae830728a3bc59c69adea Mon Sep 17 00:00:00 2001 From: Justin Drew <2396364+jdrew82@users.noreply.github.com> Date: Fri, 20 Mar 2026 11:51:07 -0500 Subject: [PATCH 2/3] Revert Addition of __new__ method. (#347) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * fix: ⏪️ Revert addition of dunder new method. * Remove import * docs: Tweak changelog statement. * test: ⏪️ Remove tests that are no longer relevant. * test: 🚨 Fix ruff --- changes/346.fixed | 1 + diffsync/__init__.py | 14 ---- tests/unit/test_diffsync.py | 127 ------------------------------------ 3 files changed, 1 insertion(+), 141 deletions(-) create mode 100644 changes/346.fixed diff --git a/changes/346.fixed b/changes/346.fixed new file mode 100644 index 0000000..7013268 --- /dev/null +++ b/changes/346.fixed @@ -0,0 +1 @@ +Reverted addition of __new__ method to Adapter class to resolve instantiation issues. diff --git a/diffsync/__init__.py b/diffsync/__init__.py index 402320e..904fedb 100644 --- a/diffsync/__init__.py +++ b/diffsync/__init__.py @@ -16,7 +16,6 @@ """ import sys -from copy import deepcopy from inspect import isclass from typing import ( Any, @@ -482,19 +481,6 @@ def __init_subclass__(cls) -> None: if not isclass(value) or not issubclass(value, DiffSyncModel): raise AttributeError(f'top_level references attribute "{name}" but it is not a DiffSyncModel subclass!') - def __new__(cls, **kwargs): # type: ignore[no-untyped-def] - """Document keyword arguments that were used to initialize Adapter.""" - meta_kwargs = {} - for key, value in kwargs.items(): - try: - meta_kwargs[key] = deepcopy(value) - except Exception: # pylint: disable=broad-exception-caught - # Some objects (e.g. Kafka Consumer, DB connections) cannot be deep copied - meta_kwargs[key] = value - instance = super().__new__(cls) - instance._meta_kwargs = meta_kwargs - return instance - def __str__(self) -> StrType: """String representation of an Adapter.""" if self.type != self.name: diff --git a/tests/unit/test_diffsync.py b/tests/unit/test_diffsync.py index 26a2f1c..4962140 100644 --- a/tests/unit/test_diffsync.py +++ b/tests/unit/test_diffsync.py @@ -9,7 +9,6 @@ from diffsync import Adapter, DiffSyncModel from diffsync.enum import DiffSyncFlags, DiffSyncModelFlags from diffsync.exceptions import DiffClassMismatch, ObjectAlreadyExists, ObjectCrudException, ObjectNotFound -from diffsync.store.local import LocalStore from .conftest import BackendA, Device, Interface, PersonA, Site, TrackedDiff @@ -1142,129 +1141,3 @@ def test_diffsync_get_initial_value_order(): "interface", "person", ] - - -def test_adapter_new_stores_kwargs(): - """Test that __new__ stores keyword arguments in _meta_kwargs.""" - adapter = Adapter(name="test_adapter") - assert hasattr(adapter, "_meta_kwargs") - assert adapter._meta_kwargs == {"name": "test_adapter"} # pylint: disable=protected-access - - -def test_adapter_new_with_no_kwargs(): - """Test that __new__ works with no keyword arguments.""" - adapter = Adapter() - assert hasattr(adapter, "_meta_kwargs") - assert adapter._meta_kwargs == {} # pylint: disable=protected-access - - -def test_adapter_new_with_multiple_kwargs(): - """Test that __new__ stores multiple keyword arguments.""" - adapter = Adapter(name="test", internal_storage_engine=LocalStore) - assert adapter._meta_kwargs == { # pylint: disable=protected-access - "name": "test", - "internal_storage_engine": LocalStore, - } - - -def test_adapter_new_with_subclass(): - """Test that __new__ works correctly with Adapter subclasses.""" - adapter = BackendA(name="test_backend") - assert hasattr(adapter, "_meta_kwargs") # pylint: disable=protected-access - assert adapter._meta_kwargs == {"name": "test_backend"} # pylint: disable=protected-access - - -def test_adapter_new_independent_instances(): - """Test that different Adapter instances have independent _meta_kwargs.""" - adapter1 = Adapter(name="adapter1", internal_storage_engine=LocalStore) - adapter2 = Adapter(name="adapter2", internal_storage_engine=LocalStore) - - assert adapter1._meta_kwargs["name"] == "adapter1" # pylint: disable=protected-access - assert adapter1._meta_kwargs["internal_storage_engine"] == LocalStore # pylint: disable=protected-access - assert adapter2._meta_kwargs["name"] == "adapter2" # pylint: disable=protected-access - assert adapter2._meta_kwargs["internal_storage_engine"] == LocalStore # pylint: disable=protected-access - - -class _AdapterWithExtraKwargs(Adapter): - """Minimal Adapter subclass that accepts extra kwargs for testing __new__ serialization.""" - - def __init__(self, name=None, internal_storage_engine=LocalStore, **kwargs): - super().__init__(name=name, internal_storage_engine=internal_storage_engine) - - -def test_adapter_new_serializable_objects_are_deep_copied(): - """Test that serializable objects passed to __new__ are deep-copied into _meta_kwargs.""" - mutable_config = {"host": "localhost", "port": 5432} - mutable_list = [1, 2, 3] - adapter = _AdapterWithExtraKwargs( - name="test", - config=mutable_config, - tags=mutable_list, - internal_storage_engine=LocalStore, - ) - - # Verify values are stored - assert adapter._meta_kwargs["config"] == {"host": "localhost", "port": 5432} # pylint: disable=protected-access - assert adapter._meta_kwargs["tags"] == [1, 2, 3] # pylint: disable=protected-access - - # Mutate the original objects - _meta_kwargs should retain the original values (deep copy) - mutable_config["port"] = 9999 - mutable_list.append(4) - - assert adapter._meta_kwargs["config"] == {"host": "localhost", "port": 5432} # pylint: disable=protected-access - assert adapter._meta_kwargs["tags"] == [1, 2, 3] # pylint: disable=protected-access - - -def test_adapter_new_non_serializable_type_error_stored_as_is(): - """Test that objects raising TypeError on deepcopy are stored as-is in _meta_kwargs.""" - - class NonCopyableTypeError: - """Object that raises TypeError when deep-copied (e.g. DB connection, Kafka Consumer).""" - - def __deepcopy__(self, memo=None): - raise TypeError("Cannot deep copy this object") - - non_copyable = NonCopyableTypeError() - adapter = _AdapterWithExtraKwargs(name="test", non_copyable=non_copyable, internal_storage_engine=LocalStore) - - assert adapter._meta_kwargs["non_copyable"] is non_copyable # pylint: disable=protected-access - - -def test_adapter_new_non_serializable_attribute_error_stored_as_is(): - """Test that objects raising AttributeError on deepcopy are stored as-is in _meta_kwargs.""" - - class NonCopyableAttributeError: - """Object that raises AttributeError when deep-copied.""" - - def __deepcopy__(self, memo=None): - raise AttributeError("Cannot deep copy - missing attribute") - - non_copyable = NonCopyableAttributeError() - adapter = _AdapterWithExtraKwargs(name="test", non_copyable=non_copyable, internal_storage_engine=LocalStore) - - assert adapter._meta_kwargs["non_copyable"] is non_copyable # pylint: disable=protected-access - - -def test_adapter_new_mixed_serializable_and_non_serializable_kwargs(): - """Test that __new__ handles mix of serializable and non-serializable kwargs correctly.""" - - class NonCopyable: - def __deepcopy__(self, memo=None): - raise TypeError("Cannot copy") - - serializable_dict = {"key": "value"} - non_copyable = NonCopyable() - - adapter = _AdapterWithExtraKwargs( - name="test", - config=serializable_dict, - connection=non_copyable, - internal_storage_engine=LocalStore, - ) - - # Serializable: deep-copied (independent copy) - assert adapter._meta_kwargs["config"] == {"key": "value"} # pylint: disable=protected-access - assert adapter._meta_kwargs["config"] is not serializable_dict - - # Non-serializable: stored by reference - assert adapter._meta_kwargs["connection"] is non_copyable # pylint: disable=protected-access From ee288d5179a9b04c3b61a258bd8697033345cd43 Mon Sep 17 00:00:00 2001 From: Justin Drew <2396364+jdrew82@users.noreply.github.com> Date: Fri, 20 Mar 2026 11:54:09 -0500 Subject: [PATCH 3/3] Bump patch and add release notes --- changes/346.fixed | 1 - docs/admin/release_notes/version_2.2.md | 6 ++++++ pyproject.toml | 2 +- 3 files changed, 7 insertions(+), 2 deletions(-) delete mode 100644 changes/346.fixed diff --git a/changes/346.fixed b/changes/346.fixed deleted file mode 100644 index 7013268..0000000 --- a/changes/346.fixed +++ /dev/null @@ -1 +0,0 @@ -Reverted addition of __new__ method to Adapter class to resolve instantiation issues. diff --git a/docs/admin/release_notes/version_2.2.md b/docs/admin/release_notes/version_2.2.md index 603c7ad..1d667e1 100644 --- a/docs/admin/release_notes/version_2.2.md +++ b/docs/admin/release_notes/version_2.2.md @@ -24,3 +24,9 @@ Remove Python 3.9 support as it's EOL. ### Fixed - [#339](https://github.com/networktocode/diffsync/issues/339) - Fixed bug with deepcopy in dunder new. + +## [v2.2.3 (2026-03-20)](https://github.com/networktocode/diffsync/releases/tag/v2.2.3) + +### Fixed + +- [#346](https://github.com/networktocode/diffsync/issues/346) - Reverted addition of __new__ method to Adapter class to resolve instantiation issues. diff --git a/pyproject.toml b/pyproject.toml index 88e5600..bf8a031 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "diffsync" -version = "2.2.3a0" +version = "2.2.3" description = "Library to easily sync/diff/update 2 different data sources" authors = ["Network to Code, LLC "] license = "Apache-2.0"