From 0bcfb286f3fc718235fb89743dd32607fbe69a32 Mon Sep 17 00:00:00 2001 From: Justin Drew <2396364+jdrew82@users.noreply.github.com> Date: Wed, 11 Feb 2026 15:26:01 -0600 Subject: [PATCH 1/3] fix: Tweak deepcopy of kwargs to handle non-serializable objects. --- diffsync/__init__.py | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/diffsync/__init__.py b/diffsync/__init__.py index b39a6b58..c48e7053 100644 --- a/diffsync/__init__.py +++ b/diffsync/__init__.py @@ -484,7 +484,13 @@ def __init_subclass__(cls) -> None: def __new__(cls, **kwargs): # type: ignore[no-untyped-def] """Document keyword arguments that were used to initialize Adapter.""" - meta_kwargs = deepcopy(kwargs) + meta_kwargs = {} + for key, value in kwargs.items(): + try: + meta_kwargs[key] = deepcopy(value) + except (TypeError, AttributeError): + # 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 From 463b74b487d01c547018e0b2cb4f14fa2d8b8ddd Mon Sep 17 00:00:00 2001 From: Justin Drew <2396364+jdrew82@users.noreply.github.com> Date: Thu, 12 Feb 2026 14:41:47 -0600 Subject: [PATCH 2/3] =?UTF-8?q?test:=20=E2=9C=85=20Add=20tests=20validatin?= =?UTF-8?q?g=20handling=20of=20serialized=20and=20non-serialized=20objects?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- tests/unit/test_diffsync.py | 85 +++++++++++++++++++++++++++++++++++++ 1 file changed, 85 insertions(+) diff --git a/tests/unit/test_diffsync.py b/tests/unit/test_diffsync.py index 924411fe..26a2f1cb 100644 --- a/tests/unit/test_diffsync.py +++ b/tests/unit/test_diffsync.py @@ -1183,3 +1183,88 @@ def test_adapter_new_independent_instances(): 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 0311f69589bd1a9fb76126cdbb11ba4470aa6011 Mon Sep 17 00:00:00 2001 From: Justin Drew <2396364+jdrew82@users.noreply.github.com> Date: Thu, 12 Feb 2026 14:47:22 -0600 Subject: [PATCH 3/3] docs: Add changelog fragment --- changes/334.fixed | 1 + 1 file changed, 1 insertion(+) create mode 100644 changes/334.fixed diff --git a/changes/334.fixed b/changes/334.fixed new file mode 100644 index 00000000..5578d075 --- /dev/null +++ b/changes/334.fixed @@ -0,0 +1 @@ +Fix TypeError being thrown when non-serializable object is used in creating a Diff/Adapter.