diff --git a/docs/source/utilities.rst b/docs/source/utilities.rst index f237925..82b5f3b 100644 --- a/docs/source/utilities.rst +++ b/docs/source/utilities.rst @@ -35,12 +35,6 @@ Utilities for Data Items .. autofunction:: balderhub.data.lib.utils.functions.full_dictionary_is_not_definable - -Additional / Special Types -========================== - -.. autoclass:: balderhub.data.lib.utils.UnorderedList - Response Messages ================= diff --git a/src/balderhub/data/lib/utils/__init__.py b/src/balderhub/data/lib/utils/__init__.py index f6f23b1..1c2294d 100644 --- a/src/balderhub/data/lib/utils/__init__.py +++ b/src/balderhub/data/lib/utils/__init__.py @@ -6,7 +6,6 @@ from .response_message_list import ResponseMessageList from .single_data_item import SingleDataItem from .single_data_item_collection import SingleDataItemCollection -from .unordered_list import UnorderedList __all__ = [ 'NOT_DEFINABLE', @@ -15,6 +14,5 @@ 'ResponseMessage', 'ResponseMessageList', 'SingleDataItem', - 'SingleDataItemCollection', - 'UnorderedList' + 'SingleDataItemCollection' ] diff --git a/src/balderhub/data/lib/utils/single_data_item.py b/src/balderhub/data/lib/utils/single_data_item.py index 42b544d..68b4052 100644 --- a/src/balderhub/data/lib/utils/single_data_item.py +++ b/src/balderhub/data/lib/utils/single_data_item.py @@ -12,7 +12,6 @@ from .functions import convert_field_lookups_to_dict_structure from .lookup_field_string import LookupFieldString from .not_definable import NOT_DEFINABLE -from .unordered_list import UnorderedList logger = logging.getLogger(__name__) @@ -73,7 +72,7 @@ def _validate_element(mcs, type_def, allow_nesting=True): if not allow_nesting: raise MisconfiguredDataItemError('nesting typing is only allowed for list') # references another type definition - if get_origin(type_def) in [list, List, UnorderedList]: + if get_origin(type_def) in [list, List]: inner_args = get_args(type_def) if len(inner_args) != 1: raise MisconfiguredDataItemError('list type definition can only have one argument for ' @@ -205,7 +204,7 @@ def get_element_type_for_list(cls, field_lookup: str | LookupFieldString) -> typ """ cleaned_type = cls.get_cleaned_field_data_type(field_lookup) - if cls.get_field_data_type(field_lookup) not in [list, UnorderedList]: + if cls.get_field_data_type(field_lookup) != list: raise TypeError(f'the referenced field `{field_lookup}` is no list (is from type `{cleaned_type}`)') if cls.is_optional_field(field_lookup): inner_args = set(get_args(cleaned_type)) @@ -361,9 +360,6 @@ def get_data_type(_of_element_type) -> type: if get_origin(_of_element_type) in [list, List]: return list - if get_origin(_of_element_type) in [UnorderedList]: - return UnorderedList - if get_origin(_of_element_type) is Optional: return get_data_type(get_args(_of_element_type)[0]) @@ -595,7 +591,7 @@ def needs_to_be_checked(self_val, other_val): f"set - self={self_value} | other={other_value}") continue - if field_data_type in [list, UnorderedList]: + if field_data_type is list: if allow_non_definable and self_value == NOT_DEFINABLE or other_value == NOT_DEFINABLE: # ignore continue @@ -607,17 +603,6 @@ def needs_to_be_checked(self_val, other_val): f"self={len(self_value)}, other={len(other_value)}") else: idx = 0 - - # TODO improve this implementation - if field_data_type is UnorderedList: - - sorted_kwargs = {} - if issubclass(inner_type, SingleDataItem): - sorted_kwargs['key'] = lambda x: x.get_unique_identification() - - self_value = sorted(self_value, **sorted_kwargs) - other_value = sorted(other_value, **sorted_kwargs) - # both lists have the same length -> start comparing items for cur_self_item, cur_other_item in zip(self_value, other_value): if not needs_to_be_checked(cur_self_item, cur_other_item): diff --git a/src/balderhub/data/lib/utils/unordered_list.py b/src/balderhub/data/lib/utils/unordered_list.py deleted file mode 100644 index d555ced..0000000 --- a/src/balderhub/data/lib/utils/unordered_list.py +++ /dev/null @@ -1,43 +0,0 @@ -from __future__ import annotations - -import copy -from typing import Any, TypeVar, get_args, get_origin - -from pydantic import GetCoreSchemaHandler -from pydantic_core import core_schema - -T = TypeVar("T") - - -class UnorderedList(list[T]): - """ - Represents a list that compares equality by its elements, regardless of their order. This class - is intended to provide functionality for unordered list comparisons, particularly useful when - order does not hold significance in operations like equality checks. - """ - def __eq__(self, other): - self_copy = copy.copy(self) - other_copy = copy.copy(other) - - return self_copy.sort() == other_copy.sort() - - @classmethod - def __get_pydantic_core_schema__( - cls, source_type: Any, handler: GetCoreSchemaHandler - ) -> core_schema.CoreSchema: - origin = get_origin(source_type) - if origin is cls: # handles both bare UnorderedList and UnorderedList[T] - args = get_args(source_type) - if args: - # Use handler.generate_schema(...) to avoid the recursion warning - item_schema = handler.generate_schema(args[0]) - return core_schema.list_schema(item_schema) - - return core_schema.list_schema(handler.generate_schema(Any)) - - # Fallback for unexpected cases - return handler.generate_schema(list) - - @classmethod - def __get_pydantic_json_schema__(cls, _core_schema, handler): - return handler(core_schema.list_schema(core_schema.any_schema())) diff --git a/tests/scenarios/scenario_utils_single_data_item_unordered_list.py b/tests/scenarios/scenario_utils_single_data_item_unordered_list.py deleted file mode 100644 index 07d4be5..0000000 --- a/tests/scenarios/scenario_utils_single_data_item_unordered_list.py +++ /dev/null @@ -1,124 +0,0 @@ -from typing import Optional -from balderhub.unit.scenarios import ScenarioUnit - -from balderhub.data.lib.utils import SingleDataItem, UnorderedList - - -# Test data item classes for testing purposes -class SimpleDataItem(SingleDataItem): - name: str - value: int - - def get_unique_identification(self): - return f"{self.name}_{self.value}" - - -class NestedSingleRef(SingleDataItem): - name: str - unsorted_list: UnorderedList[int] - unsorted_list_of_single_data_items: UnorderedList[SimpleDataItem] - optional_list_of_single_data_items: Optional[UnorderedList[SimpleDataItem]] = None - - def get_unique_identification(self): - return self.name - - -class ScenarioUtilsSingleDataItemUnsortedList(ScenarioUnit): - """Unittests for SingleDataItem class.""" - - def test_compare(self): - a_1 = NestedSingleRef( - name='a', - unsorted_list=UnorderedList([1, 2, 3]), - unsorted_list_of_single_data_items=UnorderedList() - ) - a_2 = NestedSingleRef( - name='a', - unsorted_list=UnorderedList([1, 2, 3]), - unsorted_list_of_single_data_items=UnorderedList() - ) - b = NestedSingleRef( - name='a', - unsorted_list=UnorderedList([1, 3, 2]), - unsorted_list_of_single_data_items=UnorderedList() - ) - assert a_1.compare(a_2), a_1.get_difference_error_messages(a_2) - - assert a_1.compare(b), a_1.get_difference_error_messages(b) - assert a_2.compare(b), a_2.get_difference_error_messages(b) - - def test_compare_dataitems(self): - a = NestedSingleRef( - name='a', - unsorted_list=UnorderedList([1, 2, 3]), - unsorted_list_of_single_data_items=UnorderedList([ - SimpleDataItem(name='a', value=1), - SimpleDataItem(name='b', value=2), - SimpleDataItem(name='c', value=3), - ]) - ) - b = NestedSingleRef( - name='a', unsorted_list=UnorderedList([1, 3, 2]), - unsorted_list_of_single_data_items=UnorderedList([ - SimpleDataItem(name='a', value=1), - SimpleDataItem(name='c', value=3), - SimpleDataItem(name='b', value=2), - ]) - ) - - assert a.compare(b), a.get_difference_error_messages(b) - - def test_compare_empty_lists(self): - a = NestedSingleRef( - name='a', - unsorted_list=UnorderedList(), - unsorted_list_of_single_data_items=UnorderedList() - ) - b = NestedSingleRef( - name='a', - unsorted_list=UnorderedList(), - unsorted_list_of_single_data_items=UnorderedList() - ) - assert a.compare(b), a.get_difference_error_messages(b) - - def test_optional_unsorted_list(self): - - a = NestedSingleRef( - name='a', - unsorted_list=UnorderedList([1]), - unsorted_list_of_single_data_items=UnorderedList(), - optional_list_of_single_data_items=UnorderedList([SimpleDataItem(name='x', value=1)]) - ) - b = NestedSingleRef( - name='a', - unsorted_list=UnorderedList([1]), - unsorted_list_of_single_data_items=UnorderedList(), - optional_list_of_single_data_items=UnorderedList([SimpleDataItem(name='x', value=1)]) - ) - assert a.compare(b), a.get_difference_error_messages(b) - - def test_compare_inequality(self): - a = NestedSingleRef( - name='a', - unsorted_list=UnorderedList([1, 2, 3]), - unsorted_list_of_single_data_items=UnorderedList() - ) - b = NestedSingleRef( - name='a', - unsorted_list=UnorderedList([1, 2, 4]), - unsorted_list_of_single_data_items=UnorderedList() - ) - c = NestedSingleRef( - name='a', - unsorted_list=UnorderedList([1, 2, 3, 4]), - unsorted_list_of_single_data_items=UnorderedList() - ) - assert a.get_difference_error_messages(b) == ['unsorted_list[2]: detect different value - self: `[1, 2, 3]` | other: `[1, 2, 4]`'], a.get_difference_error_messages(b) - assert a.get_difference_error_messages(c) == ['unsorted_list: detect different list length - self=3, other=4'], a.get_difference_error_messages(c) - assert b.get_difference_error_messages(c) == ['unsorted_list: detect different list length - self=3, other=4'], b.get_difference_error_messages(c) - - - def test_get_element_type_for(self): - assert NestedSingleRef.get_field_data_type('unsorted_list') == UnorderedList - assert NestedSingleRef.get_element_type_for_list('unsorted_list') == int - assert NestedSingleRef.get_element_type_for_list('unsorted_list_of_single_data_items') == SimpleDataItem diff --git a/tests/scenarios/scenario_utils_unordered_list.py b/tests/scenarios/scenario_utils_unordered_list.py deleted file mode 100644 index a755471..0000000 --- a/tests/scenarios/scenario_utils_unordered_list.py +++ /dev/null @@ -1,80 +0,0 @@ -from __future__ import annotations - - -from pydantic import BaseModel - -from balderhub.unit.scenarios import ScenarioUnit - -from balderhub.data.lib.utils import UnorderedList - - -class ScenarioUtilsUnorderedList(ScenarioUnit): - """Unittests for UnorderedList class.""" - - def test_eq_identical_order(self): - a = UnorderedList([1, 2, 3]) - b = UnorderedList([1, 2, 3]) - assert a == b - - def test_eq_different_order(self): - a = UnorderedList([1, 2, 3]) - b = UnorderedList([3, 2, 1]) - assert a == b - - def test_eq_different_content(self): - a = UnorderedList([1, 2, 3]) - b = UnorderedList([1, 2, 4]) - assert a != b - - def test_eq_empty(self): - a = UnorderedList() - b = UnorderedList() - assert a == b - - def test_eq_with_list(self): - a = UnorderedList([1, 2, 3]) - b = [3, 2, 1] - assert a == b - - def test_pydantic_model(self): - class Model(BaseModel): - items: UnorderedList[int] - - m = Model(items=[3, 1, 2]) - assert isinstance(m.items, list) - assert m.items == [3, 1, 2] - assert m.model_dump() == {"items": [3, 1, 2]} - - def test_pydantic_optional(self): - class Model(BaseModel): - items: UnorderedList[int] | None = None - - m = Model(items=[3, 1, 2]) - assert m.items == [3, 1, 2] - - def test_ne_different_types(self): - a = UnorderedList([1, 2, 3]) - assert a != "not a list" - assert a != 123 - assert a != None - assert a != {"a": 1} - - def test_ne_different_lengths(self): - a = UnorderedList([1, 2, 3]) - b = UnorderedList([1, 2]) - assert a != b - - def test_ne_with_duplicates(self): - a = UnorderedList([1, 1, 2]) - b = UnorderedList([1, 2, 2]) - assert a != b - - def test_eq_with_duplicates(self): - a = UnorderedList([1, 2, 1]) - b = UnorderedList([2, 1, 1]) - assert a == b - - def test_ne_vs_plain_list_different(self): - a = UnorderedList([1, 2, 3]) - b = [1, 2, 4] - assert a != b