Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions changes/346.fixed
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Reverted addition of __new__ method to Adapter class to resolve instantiation issues.
14 changes: 0 additions & 14 deletions diffsync/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,6 @@
"""

import sys
from copy import deepcopy
from inspect import isclass
from typing import (
Any,
Expand Down Expand Up @@ -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:
Expand Down
127 changes: 0 additions & 127 deletions tests/unit/test_diffsync.py
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down Expand Up @@ -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
Loading