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/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 34267ec..bf8a031 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "diffsync" -version = "2.2.2" +version = "2.2.3" description = "Library to easily sync/diff/update 2 different data sources" authors = ["Network to Code, LLC "] license = "Apache-2.0" 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