diff --git a/src/lifeblood/core_nodes/rename_attrib.py b/src/lifeblood/core_nodes/rename_attrib.py index 05eb32e0..82e138de 100644 --- a/src/lifeblood/core_nodes/rename_attrib.py +++ b/src/lifeblood/core_nodes/rename_attrib.py @@ -1,5 +1,5 @@ from lifeblood.node_plugin_base import BaseNode -from lifeblood.nodethings import ProcessingResult +from lifeblood.nodethings import ProcessingResult, ProcessingError from lifeblood.taskspawn import TaskSpawn from lifeblood.exceptions import NodeNotReadyToProcess from lifeblood.enums import NodeParameterType @@ -29,6 +29,7 @@ def __init__(self, name: str): super(RenameAttributes, self).__init__(name) ui = self.get_ui() with ui.initializing_interface_lock(): + ui.add_parameter('ignore errors', 'Ignore non-existing attribute errors', NodeParameterType.BOOL, False) with ui.multigroup_parameter_block('num'): with ui.parameters_on_same_line_block(): ui.add_parameter('oldname', '', NodeParameterType.STRING, 'from') @@ -36,21 +37,36 @@ def __init__(self, name: str): def process_task(self, context) -> ProcessingResult: res = ProcessingResult() + do_ignore_errors = context.param_value('ignore errors') attrs = dict(context.task_attributes()) for i in range(context.param_value('num')): - attr_oldname = context.param_value(f'oldname_{i}') + attr_oldname: str = context.param_value(f'oldname_{i}').strip() + if not attr_oldname: + if do_ignore_errors: + continue + else: + raise ProcessingError(f'from-attribute name must not be empty') if attr_oldname not in attrs: - continue + if do_ignore_errors: + continue + else: + raise ProcessingError(f'attribute "{attr_oldname}" does not exist') + + attr_newname: str = context.param_value(f'newname_{i}').strip() + if not attr_newname: + if do_ignore_errors: + continue + else: + raise ProcessingError(f'to-attribute name must not be empty') - attr_newname = context.param_value(f'newname_{i}') if attr_newname == attr_oldname: continue res.set_attribute(attr_newname, attrs[attr_oldname]) res.remove_attribute(attr_oldname) attrs[attr_newname] = attrs[attr_oldname] - del attrs[attr_oldname] + attrs.pop(attr_oldname) return res diff --git a/tests/nodes/test_rename_attrib.py b/tests/nodes/test_rename_attrib.py new file mode 100644 index 00000000..f01a1ce6 --- /dev/null +++ b/tests/nodes/test_rename_attrib.py @@ -0,0 +1,170 @@ +import random +from asyncio import Event +from lifeblood.scheduler import Scheduler +from lifeblood.worker import Worker +from lifeblood.nodethings import ProcessingError +from lifeblood_testing_common.nodes_common import TestCaseBase, PseudoContext + +from typing import List + + +class TestWedge(TestCaseBase): + async def test_noop(self): + async def _logic(sched: Scheduler, workers: List[Worker], done_waiter: Event, context: PseudoContext): + task = context.create_pseudo_task_with_attrs({'foo': 123, 'bar': 'wow'}) + + node = context.create_node('rename_attrib', 'footest') + + res = context.process_task(node, task) + self.assertEqual({}, res.attributes_to_set) + + await self._helper_test_node_with_arg_update( + _logic + ) + + async def test_same(self): + async def _logic(sched: Scheduler, workers: List[Worker], done_waiter: Event, context: PseudoContext): + task = context.create_pseudo_task_with_attrs({'foo': 123, 'bar': 'wow'}) + + node = context.create_node('rename_attrib', 'footest') + node.set_param_value('num', 1) + node.set_param_value('oldname_0', 'foo') + node.set_param_value('newname_0', 'foo') + + res = context.process_task(node, task) + self.assertEqual({}, res.attributes_to_set) + + await self._helper_test_node_with_arg_update( + _logic + ) + + async def test_basic(self): + async def _logic(sched: Scheduler, workers: List[Worker], done_waiter: Event, context: PseudoContext): + for pref, suff in (('', ''), (' ', ''), (' ', ''), ('', ' '), ('', ' '), (' ', ' '), (' ', ' '), (' ', ' ')): + task = context.create_pseudo_task_with_attrs({'foo': 123, 'bar': 'wow'}) + + node = context.create_node('rename_attrib', 'footest') + node.set_param_value('num', 1) + node.set_param_value('oldname_0', f'{pref}foo{suff}') + node.set_param_value('newname_0', f'{pref}bla{suff}') + + res = context.process_task(node, task) + self.assertEqual({ + 'bla': 123, + 'foo': None, + }, res.attributes_to_set) + + await self._helper_test_node_with_arg_update( + _logic + ) + + async def test_chain(self): + async def _logic(sched: Scheduler, workers: List[Worker], done_waiter: Event, context: PseudoContext): + + task = context.create_pseudo_task_with_attrs({'foo': 123, 'bar': 'wow'}) + + node = context.create_node('rename_attrib', 'footest') + node.set_param_value('ignore errors', False) + node.set_param_value('num', 2) + node.set_param_value('oldname_0', f'foo') + node.set_param_value('newname_0', f'middle') + node.set_param_value('oldname_1', f'middle') + node.set_param_value('newname_1', f'bla') + + res = context.process_task(node, task) + self.assertEqual({ + 'bla': 123, + 'foo': None, + 'middle': None, + }, res.attributes_to_set) + + await self._helper_test_node_with_arg_update( + _logic + ) + + async def test_overlap(self): + async def _logic(sched: Scheduler, workers: List[Worker], done_waiter: Event, context: PseudoContext): + task = context.create_pseudo_task_with_attrs({'foo': 123, 'bar': 'wow'}) + + node = context.create_node('rename_attrib', 'footest') + node.set_param_value('num', 1) + node.set_param_value('oldname_0', 'foo') + node.set_param_value('newname_0', 'bar') + + res = context.process_task(node, task) + self.assertEqual({ + 'bar': 123, + 'foo': None, + }, res.attributes_to_set) + + await self._helper_test_node_with_arg_update( + _logic + ) + + async def test_nonexisting_error(self): + async def _logic(sched: Scheduler, workers: List[Worker], done_waiter: Event, context: PseudoContext): + task = context.create_pseudo_task_with_attrs({'foo': 123, 'bar': 'wow'}) + + node = context.create_node('rename_attrib', 'footest') + node.set_param_value('ignore errors', False) + node.set_param_value('num', 1) + node.set_param_value('oldname_0', 'faaaaa') + node.set_param_value('newname_0', 'bla') + + self.assertRaises(ProcessingError, context.process_task, node, task) + + await self._helper_test_node_with_arg_update( + _logic + ) + + async def test_nonexisting_noerror(self): + async def _logic(sched: Scheduler, workers: List[Worker], done_waiter: Event, context: PseudoContext): + task = context.create_pseudo_task_with_attrs({'foo': 123, 'bar': 'wow'}) + + node = context.create_node('rename_attrib', 'footest') + node.set_param_value('ignore errors', True) + node.set_param_value('num', 1) + node.set_param_value('oldname_0', 'faaaaa') + node.set_param_value('newname_0', 'bla') + + res = context.process_task(node, task) + self.assertEqual({}, res.attributes_to_set) + + await self._helper_test_node_with_arg_update( + _logic + ) + + async def test_empty_error(self): + async def _logic(sched: Scheduler, workers: List[Worker], done_waiter: Event, context: PseudoContext): + for from_attr, to_attr in [('', 'foo'), ('foo', ''), ('', ''), (' ', ' '), ('bar', ' '), (' ', 'bar')]: + task = context.create_pseudo_task_with_attrs({'foo': 123, 'bar': 'wow'}) + + node = context.create_node('rename_attrib', 'footest') + node.set_param_value('ignore errors', False) + node.set_param_value('num', 1) + node.set_param_value('oldname_0', from_attr) + node.set_param_value('newname_0', to_attr) + + self.assertRaises(ProcessingError, context.process_task, node, task) + + await self._helper_test_node_with_arg_update( + _logic + ) + + async def test_empty_noerror(self): + async def _logic(sched: Scheduler, workers: List[Worker], done_waiter: Event, context: PseudoContext): + for from_attr, to_attr in [('', 'foo'), ('foo', ''), ('', ''), (' ', ' '), ('bar', ' '), (' ', 'bar')]: + task = context.create_pseudo_task_with_attrs({'foo': 123, 'bar': 'wow'}) + + node = context.create_node('rename_attrib', 'footest') + node.set_param_value('ignore errors', True) + node.set_param_value('num', 1) + node.set_param_value('oldname_0', from_attr) + node.set_param_value('newname_0', to_attr) + + res = context.process_task(node, task) + self.assertEqual({}, res.attributes_to_set) # expect noop + + await self._helper_test_node_with_arg_update( + _logic + )