Summary of what goes wrong
I'm trying to add a root widget to an existing .kv app, where said arbitrary root widget is created by a kivy.lang.Builder.load_string method. This will work fine if the kivy string supplied to the Builder represents valid and legal .kv code. Expectedly, it will fail otherwise.
To account for this, I've added a try - except block, in the hopes of catching any error that may have caused the failure of adding appropriate kivy widgets.
The corresponding Exception is then used inside a popup-message, After which the invalid widgets are ultimately
not supposed to be added.
For some inputs this works as expected (shows popup message if error). However, for specific string inputs, the app crashes without catching the responsible errors. Now I'm wondering why these errors were not caught, and how to catch them correctly. See beneath for the exact code.
My Application
My app consists of one .py and one .kv file1, as below (simplified):
# main.kv
ScreenManager:
Screen:
name: 'string_screen'
BoxLayout:
orientation: 'vertical'
TextInput:
id: code_text
text: app.text
Button:
text: 'call'
on_release: app.call()
Screen:
name: 'called_screen'
BoxLayout:
id: render_layout
<Button>:
size_hint: 0.5, None
height: '1.2cm'
<MsgPopup>:
size_hint: .75, .6
title: "Attention"
BoxLayout:
orientation: 'vertical'
padding: 10
spacing: 20
Label:
id: message_label
size_hint_y: 0.4
text: "Label"
Button:
text: 'Dismiss'
size_hint_y: 0.4
on_press: root.dismiss()
And the python file:
# main.py
from kivy.app import App
from kivy.properties import StringProperty
from kivy.lang import Builder
from kivy.uix.popup import Popup
class MainApp(App):
text = StringProperty()
kv = None
def call(self):
kv_text = self.root.ids['code_text'].text
try:
self.kv = Builder.load_string(kv_text)
print(self.kv)
self.root.ids['render_layout'].clear_widgets()
print('cleared')
self.root.ids['render_layout'].add_widget(self.kv)
print('added')
self.root.current = 'called_screen'
self.root.transition.direction = 'left'
print('swiped')
except Exception as e:
popup = MsgPopup(e)
popup.open()
class MsgPopup(Popup):
def __init__(self, msg):
super().__init__()
self.ids.message_label.text = str(msg)
if __name__ == '__main__':
MainApp().run()
1 My actually app consists of some additional and more elaborated code, but this simplified version of it is sufficient for reproducing the undesired behavior.
As you can see in the code, the app consists of two screens. The main element in the first is the TextInput from which the second one is created. The below two images demonstrate when there is no error.
Below is an example of correct behavior, when the text input contains anything that produces an error:
Unexpected Behavior
This last image shows correctly a popup message. However, when I enter the following input in the TextInput field for example:
FloatLayout:
Label:
text: "Hello World"
pos_hint: 0.5, 0.7
Which is an error in the pos_hint value argument. Then the app crashes once I press the call Button. And Instead of the expected popup message, I get an actual stack trace!
<kivy.uix.floatlayout.FloatLayout object at 0x0000016F7F0FC800>
cleared
File "C:/Users/ajdin/.PyCharmCE2019.1/config/scratches/scratch_1.py", line 34, in <module>
added
swiped
MainApp().run()
File "C:\Users\ajdin\Anaconda3\lib\site-packages\kivy\app.py", line 826, in run
runTouchApp()
File "C:\Users\ajdin\Anaconda3\lib\site-packages\kivy\base.py", line 502, in runTouchApp
EventLoop.window.mainloop()
File "C:\Users\ajdin\Anaconda3\lib\site-packages\kivy\core\window\window_sdl2.py", line 727, in mainloop
self._mainloop()
File "C:\Users\ajdin\Anaconda3\lib\site-packages\kivy\core\window\window_sdl2.py", line 460, in _mainloop
EventLoop.idle()
File "C:\Users\ajdin\Anaconda3\lib\site-packages\kivy\base.py", line 346, in idle
Clock.tick_draw()
File "C:\Users\ajdin\Anaconda3\lib\site-packages\kivy\clock.py", line 588, in tick_draw
self._process_events_before_frame()
File "kivy\_clock.pyx", line 427, in kivy._clock.CyClockBase._process_events_before_frame
File "kivy\_clock.pyx", line 467, in kivy._clock.CyClockBase._process_events_before_frame
File "kivy\_clock.pyx", line 465, in kivy._clock.CyClockBase._process_events_before_frame
File "kivy\_clock.pyx", line 167, in kivy._clock.ClockEvent.tick
File "C:\Users\ajdin\Anaconda3\lib\site-packages\kivy\uix\floatlayout.py", line 116, in do_layout
for key, value in c.pos_hint.items():
AttributeError: 'tuple' object has no attribute 'items'
Process finished with exit code 1
My expected output here would be similar to before: The above shown stack trace message shown in a popup window, without the application crashing!. I expect that because of the way I handle the exception in the button callback:
kv_text = self.root.ids['code_text'].text
try:
self.kv = Builder.load_string(kv_text)
print(self.kv)
self.root.ids['called_screen'].clear_widgets()
print('cleared')
self.root.ids['called_screen'].add_widget(self.kv)
print('added')
self.root.current = 'called_screen'
self.root.transition.direction = 'left'
print('swiped')
except Exception as e:
popup = MsgPopup(e)
popup.open()
So if there were an error within the load_string method, I expect to catch it. Otherwise if it passes that somehow, I expect to catch an error in the add_widget method. However, from the stack trace above it seems that it passes all these statements succesfully with the erroneous text input given!. You can see this from the printed outputs in the stack trace:
...
<kivy.uix.floatlayout.FloatLayout object at 0x0000016F7F0FC800>
cleared
File "C:/Users/ajdin/.PyCharmCE2019.1/config/scratches/scratch_1.py", line 34, in <module>
added
swiped
...
It prints all the statements in the try block, signaling that it passed through it without any errors, right?
Question
So if the above thrown error was not caught, what caused it, and how/where do I catch it properly, such that the app ends up with the intended behavior (An error pop up message at most)?


