diff --git a/src/yota/__init__.py b/src/yota/__init__.py index 861e9bb..f002848 100644 --- a/src/yota/__init__.py +++ b/src/yota/__init__.py @@ -7,6 +7,10 @@ import copy class TrackingMeta(type): + reserved_attr_names = ('context', 'hidden', 'g_context', 'start_template', + 'close_template', 'auto_start_close', '_renderer', + '_processor', 'name') + def __init__(mcs, name, bases, dict): """ Process all of the attributes in the `Form` (or subclass) declaration and place them accordingly. This builds the internal @@ -16,6 +20,10 @@ def __init__(mcs, name, bases, dict): mcs._validation_list = [] for name, value in dict.items(): if isinstance(value, Node): + if name in TrackingMeta.reserved_attr_names: + raise AttributeError( + '{0} is a forbidden attribute name for a Node because' + ' it overlaps with a Form attribute. Please rename.') value._attr_name = name t[value._create_counter] = value if hasattr(value, 'validators'): @@ -226,6 +234,17 @@ def insert(self, position, new_node_list): new_node_list = (new_node_list,) for i, new_node in enumerate(new_node_list): + + # Check to prevent shooting yourself in the foot + if new_node._attr_name in TrackingMeta.reserved_attr_names: + raise AttributeError('{0} is a forbidden attribute name for a' + 'Node because it overlaps with a Form attribute. Please rename.') + + # Another clarity error message + if not new_node._attr_name: + raise AttributeError('Dynamically added nodes should have an ' + '_attr_name attribute.') + if position == -1: self._node_list.append(new_node) else: diff --git a/src/yota/tests/test_form.py b/src/yota/tests/test_form.py index ca4f90d..b151217 100755 --- a/src/yota/tests/test_form.py +++ b/src/yota/tests/test_form.py @@ -220,6 +220,21 @@ def success_header_generate(self): piecewise=True) assert(success is True) + def test_node_attr_safety(self): + """ Ensure safe node _attr_names """ + + def stupid_2_6(): + class TForm(yota.Form): + name = EntryNode() + + self.assertRaises(AttributeError, stupid_2_6) + f = yota.Form() + self.assertRaises(AttributeError, f.insert, 0, EntryNode()) + self.assertRaises(AttributeError, + f.insert, + 0, + EntryNode(_attr_name='g_context')) + class TestExtra(unittest.TestCase): def test_get_by_attr(self): class TForm(yota.Form):