diff --git a/kivy/_event.pyx b/kivy/_event.pyx index 344f0c36c..ca6a073e8 100644 --- a/kivy/_event.pyx +++ b/kivy/_event.pyx @@ -228,7 +228,19 @@ cdef class EventDispatcher(ObjectWithUid): prop_args = { k: kwargs.pop(k) for k in list(kwargs.keys()) if k in properties} self._kwargs_applied_init = set(prop_args.keys()) if prop_args else set() - super(EventDispatcher, self).__init__(**kwargs) + + # at this point any kwargs passed may go to object + # which will raise a TypeError and cause confusion + try: + super(EventDispatcher, self).__init__(**kwargs) + except TypeError as e: + if kwargs and str(e).startswith("object.__init__() takes"): + raise TypeError( + 'Properties {} passed to __init__ may not be existing ' + 'property names. Valid properties are {}'.format( + sorted(kwargs.keys()), sorted(properties.keys()))) from e + else: + raise __cls__ = self.__class__ if __cls__ not in cache_events_handlers: diff --git a/kivy/tests/test_properties.py b/kivy/tests/test_properties.py index b444b4610..9e215d645 100644 --- a/kivy/tests/test_properties.py +++ b/kivy/tests/test_properties.py @@ -1266,3 +1266,67 @@ def test_inherit_property(): event.b = 'goodbye' assert event.b == 'goodbye' assert args == (event, 'goodbye') + + +def test_unknown_property(): + from kivy.properties import NumericProperty + + class MyWidget(EventDispatcher): + width = NumericProperty(0) + + with pytest.raises(TypeError) as cm: + MyWidget(width=12, unkn="abc") + assert "Properties ['unkn'] passed to __init__ may not be existing " \ + "property names. Valid properties are ['width']" \ + == str(cm.value) + + +def test_known_property_multiple_inheritance(): + + class Behavior: + def __init__(self, name): + print(f'Behavior: {self}, name={name}') + super().__init__() + + class Widget2(Behavior, EventDispatcher): + pass + + class Widget3(EventDispatcher, Behavior): + pass + + with pytest.raises(TypeError) as cm: + EventDispatcher(name='Pasta') + assert "Properties ['name'] passed to __init__ may not be existing" \ + in str(cm.value) + + Widget2(name='Pasta') # does not raise a ValueError + Widget3(name='Pasta') # does not raise a ValueError + + +def test_pass_other_typeerror(): + + class Behavior: + def __init__(self, name): + super().__init__() + raise TypeError("this is a typeerror unrelated to object") + + class Widget2(Behavior, EventDispatcher): + pass + + class Widget3(EventDispatcher, Behavior): + pass + + for cls in [Widget2, Widget3]: + with pytest.raises(TypeError) as cm: + cls(name='Pasta') + assert "this is a typeerror unrelated to object" == str(cm.value) + + +def test_object_init_error(): # the above 3 test rely on this + class TestCls(object): + def __init__(self, **kwargs): + super(TestCls, self).__init__(**kwargs) + + with pytest.raises(TypeError) as cm: + TestCls(name='foo') + assert str(cm.value).startswith("object.__init__() takes")