From 8a588270f6d3fd6c1d70e56d20ca6c0c23d439ad Mon Sep 17 00:00:00 2001 From: Jakob Ketterl Date: Tue, 9 May 2023 17:42:03 +0200 Subject: [PATCH] implement more list transformation events --- owrx/active/list/__init__.py | 72 +++++++++++++++---- test/owrx/active/list/test_active_list.py | 31 ++++++-- .../list/test_advanced_transformation.py | 34 +++++++++ 3 files changed, 121 insertions(+), 16 deletions(-) create mode 100644 test/owrx/active/list/test_advanced_transformation.py diff --git a/owrx/active/list/__init__.py b/owrx/active/list/__init__.py index e009eaac..ffbaf149 100644 --- a/owrx/active/list/__init__.py +++ b/owrx/active/list/__init__.py @@ -1,4 +1,6 @@ from abc import ABC, abstractmethod +from typing import Union +from functools import partial import logging logger = logging.getLogger(__name__) @@ -43,10 +45,29 @@ class ActiveListListener(ABC): pass +class ActiveListTransformation(ABC): + @abstractmethod + def transform(self, value): + pass + + def __call__(self, *args, **kwargs): + return self.transform(*args, **kwargs) + + def monitor(self, member, callback: callable): + pass + + def unmonitor(self, member): + pass + + class ActiveListTransformationListener(ActiveListListener): - def __init__(self, transformation: callable, target: "ActiveList"): + def __init__(self, transformation: callable, source: "ActiveList", target: "ActiveList"): self.transformation = transformation + self.source = source self.target = target + if isinstance(transformation, ActiveListTransformation): + for idx, v in enumerate(self.source): + transformation.monitor(v, partial(self._onMonitor, idx)) def onListChange(self, source: "ActiveList", changes: list[ActiveListChange]): for change in changes: @@ -57,6 +78,9 @@ class ActiveListTransformationListener(ActiveListListener): elif isinstance(change, ActiveListIndexDeleted): del self.target[change.index] + def _onMonitor(self, idx): + self.target[idx] = self.transformation(self.source[idx]) + class ActiveListFilterListener(ActiveListListener): def __init__(self, filter: callable, keyMap: list, target: "ActiveList"): @@ -91,21 +115,39 @@ class ActiveListFlattenListener(ActiveListListener): def __init__(self, source: "ActiveList", target: "ActiveList"): self.source = source self.target = target + self.source.addListener(self) for member in self.source: member.addListener(self) def getOffsetFor(self, source: "ActiveList"): idx = self.source.index(source) + return self.getOffsetForIndex(idx) + + def getOffsetForIndex(self, idx: int): return sum(len(s) for s in self.source[0:idx]) def onListChange(self, source: "ActiveList", changes: list[ActiveListChange]): for change in changes: - if isinstance(change, ActiveListIndexAdded): - self.target.insert(self.getOffsetFor(source) + change.index, change.newValue) - elif isinstance(change, ActiveListIndexUpdated): - self.target[self.getOffsetFor(source) + change.index] = change.newValue - elif isinstance(change, ActiveListIndexDeleted): - del self.target[self.getOffsetFor(source) + change.index] + if source is self.source: + if isinstance(change, ActiveListIndexAdded): + idx = self.getOffsetForIndex(change.index) + for n, v in enumerate(change.newValue): + self.target.insert(idx + n, v) + elif isinstance(change, ActiveListIndexUpdated): + idx = self.getOffsetForIndex(change.index) + del self.target[idx, idx + len(change.oldValue)] + for n, v in enumerate(change.newValue): + self.target.insert(idx + n, v) + elif isinstance(change, ActiveListIndexDeleted): + idx = self.getOffsetForIndex(change.index) + del self.target[idx, idx + len(change.oldValue)] + else: + if isinstance(change, ActiveListIndexAdded): + self.target.insert(self.getOffsetFor(source) + change.index, change.newValue) + elif isinstance(change, ActiveListIndexUpdated): + self.target[self.getOffsetFor(source) + change.index] = change.newValue + elif isinstance(change, ActiveListIndexDeleted): + del self.target[self.getOffsetFor(source) + change.index] class ActiveList: @@ -144,9 +186,9 @@ class ActiveList: def index(self, value): return self.delegate.index(value) - def map(self, transform: callable): + def map(self, transform: Union[callable, ActiveListTransformation]): res = ActiveList([transform(v) for v in self]) - self.addListener(ActiveListTransformationListener(transform, res)) + self.addListener(ActiveListTransformationListener(transform, self, res)) return res def filter(self, filter: callable): @@ -172,9 +214,15 @@ class ActiveList: self.__fireChanges([ActiveListIndexUpdated(key, oldValue, value)]) def __delitem__(self, key): - oldValue = self.delegate[key] - del self.delegate[key] - self.__fireChanges([ActiveListIndexDeleted(key, oldValue)]) + if isinstance(key, tuple): + start, stop = key + changes = [ActiveListIndexDeleted(start + idx, v) for idx, v in enumerate(self.delegate[start:stop])] + del self.delegate[start:stop] + self.__fireChanges(changes) + else: + oldValue = self.delegate[key] + del self.delegate[key] + self.__fireChanges([ActiveListIndexDeleted(key, oldValue)]) def __getitem__(self, key): return self.delegate[key] diff --git a/test/owrx/active/list/test_active_list.py b/test/owrx/active/list/test_active_list.py index 24e57359..3dc661f0 100644 --- a/test/owrx/active/list/test_active_list.py +++ b/test/owrx/active/list/test_active_list.py @@ -191,7 +191,7 @@ class ActiveListTest(TestCase): self.assertEqual(flattenedList[2], 3) self.assertEqual(flattenedList[3], 4) - def testActiveFlattenAdd(self): + def testActiveFlattenMemberAppend(self): sublist = ActiveList([3, 4]) list = ActiveList([ActiveList([1, 2]), sublist, ActiveList([6, 7])]) flattenedList = list.flatten() @@ -199,7 +199,7 @@ class ActiveListTest(TestCase): self.assertEqual(len(flattenedList), 7) self.assertEqual(flattenedList[4], 5) - def testActiveFlattenInsert(self): + def testActiveFlattenMemberInsert(self): sublist = ActiveList([3, 5]) list = ActiveList([ActiveList([1, 2]), sublist, ActiveList([6, 7])]) flattenedList = list.flatten() @@ -207,7 +207,23 @@ class ActiveListTest(TestCase): self.assertEqual(len(flattenedList), 7) self.assertEqual(flattenedList[3], 4) - def testActiveFlattenUpdate(self): + def testActiveFlattenListInsert(self): + list = ActiveList([ActiveList([1, 2]), ActiveList([6, 7])]) + flattenedList = list.flatten() + sublist = ActiveList([3, 4]) + list.insert(1, sublist) + self.assertEqual(len(flattenedList), 6) + self.assertEqual(flattenedList[2], 3) + + def testActiveFlattenListUpdate(self): + sublist = ActiveList([3, 9, 5]) + list = ActiveList([ActiveList([1, 2]), sublist, ActiveList([6, 7])]) + flattenedList = list.flatten() + sublist = ActiveList([3, 4, 5]) + list[1] = sublist + self.assertEqual(flattenedList[3], 4) + + def testActiveFlattenMemberUpdate(self): sublist = ActiveList([3, 9, 5]) list = ActiveList([ActiveList([1, 2]), sublist, ActiveList([6, 7])]) flattenedList = list.flatten() @@ -215,10 +231,17 @@ class ActiveListTest(TestCase): self.assertEqual(len(flattenedList), 7) self.assertEqual(flattenedList[3], 4) - def testActiveFlattenDelete(self): + def testActiveFlattenMemberDelete(self): sublist = ActiveList([3, 4, 9, 5]) list = ActiveList([ActiveList([1, 2]), sublist, ActiveList([6, 7])]) flattenedList = list.flatten() del sublist[2] self.assertEqual(len(flattenedList), 7) self.assertEqual(flattenedList[4], 5) + + def testActiveFlattenListDelete(self): + list = ActiveList([ActiveList([1, 2]), ActiveList([9]), ActiveList([3, 4]), ActiveList([5, 6])]) + flattenedList = list.flatten() + del list[1] + self.assertEqual(len(flattenedList), 6) + self.assertEqual(flattenedList[2], 3) diff --git a/test/owrx/active/list/test_advanced_transformation.py b/test/owrx/active/list/test_advanced_transformation.py new file mode 100644 index 00000000..7417bdac --- /dev/null +++ b/test/owrx/active/list/test_advanced_transformation.py @@ -0,0 +1,34 @@ +from owrx.active.list import ActiveList, ActiveListTransformation +from unittest import TestCase + + +class TestTranformation(ActiveListTransformation): + def __init__(self): + self.callback = None + self.prefix = "value" + + def transform(self, value): + return "{}{}".format(self.prefix, value) + + def monitor(self, member, callback: callable): + self.callback = callback + + def trigger(self, newPrefix: str): + self.prefix = newPrefix + self.callback() + + +class AdvancedTransformationTest(TestCase): + def testListAdvancedTransformation(self): + list = ActiveList([1, 2]) + transformedList = list.map(TestTranformation()) + self.assertEqual(len(transformedList), 2) + self.assertEqual(transformedList[0], "value1") + self.assertEqual(transformedList[1], "value2") + + def testListMonitor(self): + list = ActiveList([1, 2]) + transformation = TestTranformation() + transformedList = list.map(transformation) + transformation.trigger("foobar") + self.assertEqual(transformedList[1], "foobar2")