kivyattributeerrordynamic-class-creation

Kivy: Attribute error when dynamically adding screen widget


I am trying to create in instance of ProjectScreen dynamic class when add_project_screen is called. I want to create various instances of ProjectScreen, each with a different name, which is obtained from project_name_text_input. Here are my .py and .kv files:

from kivy.app import App

from kivy.uix.screenmanager import ScreenManager, Screen
from kivy.uix.boxlayout import BoxLayout
from kivy.properties import ObjectProperty
from kivy.uix.listview import ListItemButton
from kivy.factory import Factory

class ProjectListButton(ListItemButton):
    pass

class HomeScreen(Screen):
    pass

class ProjectListScreen(Screen):
    project_list = ObjectProperty(None)

class ProjectScreen(Screen):
    pass


class NewProjectScreen(Screen):
    project_name_text_input = ObjectProperty(None)
    project_address_text_input = ObjectProperty(None)
    project_scope_text_input = ObjectProperty(None)


    def add_project_list_item(self,project_name_text_input):
        new_project_name = project_name_text_input.text
        project_list_screen = self.manager.get_screen('project_list_screen')
        project_list_screen.project_list.adapter.data.extend([new_project_name])
        # Reset the ListView
        project_list_screen.project_list._trigger_reset_populate()
        #project_name_text_input.text = ''

    def add_project_screen(self,project_name_text_input, project_address_text_input, project_scope_text_input):
        name = project_name_text_input.text
        s = Factory.ProjectScreen(name=name)
        self.manager.add_widget(s)

class ReportingApp(App):
    def build(self):
        screen_manager = ScreenManager()
        screen_manager.add_widget(HomeScreen(name="home_screen"))
        screen_manager.add_widget(ProjectListScreen(name="project_list_screen"))
        screen_manager.add_widget(NewProjectScreen(name="new_project_screen"))
        return screen_manager

if __name__ == '__main__':
    ReportingApp().run()

and here is .kv file:

#:kivy 1.10.1

#: import main main
#: import ListAdapter kivy.adapters.listadapter.ListAdapter
#: import ListItemButton kivy.uix.listview.ListItemButton


<HomeScreen>:
BoxLayout:
    orientation: "vertical"
    padding: 100
    spacing: 25
    Label:
        size_hint_y: None
        height: 150
        text: "Site Visit Reporting App v1.1"

    Button:
        text: "New Project"
        on_press:
            root.manager.transition.direction = 'left'
            root.manager.current = 'new_project_screen'

    Button:
        text: "Project List"
        on_press:
            root.manager.transition.direction = 'left'
            root.manager.current = 'project_list_screen'

<NewProjectScreen>:

project_name_text_input: project_name
project_address_text_input: project_address
project_scope_text_input: project_scope

BoxLayout:
    orientation: "vertical"

    Label:
        text: 'Project Name:'
    TextInput:
        id: project_name

    Label:
        text: 'Project Address:'
    TextInput:
        id: project_address

    Label:
        text: 'Project Scope:'
    TextInput:
        id: project_scope

    BoxLayout:
        padding: 15
        spacing: 25
        Button:
            text: 'OK'
            on_press:
                root.add_project_list_item(project_name)
                root.add_project_screen(project_name, project_address, project_scope)
        Button:
            text: 'Back'
            on_press:
                root.manager.transition.direction = 'right'
                root.manager.current = 'home_screen'

<ProjectListScreen>:

project_list: project_list_view

BoxLayout:
    orientation: 'vertical'
    Label:
        text: 'Current list of projects'
    Button:
        text: 'Back'
        on_press:
            root.manager.transition.direction = 'right'
            root.manager.current = 'home_screen'

    ListView:
        id: project_list_view
        adapter:
            ListAdapter(data=[], cls=main.ProjectListButton)

<ProjectScreen@Screen>:
    Label:
        text: 'hook up project info here'
    button:
        text: 'Back'

Here are my error messages:

C:\Users\amars\AppData\Local\Programs\Python\Python37-32\python.exe C:/Users/amars/PycharmProjects/ReportingApp/main.py
[INFO   ] [Logger      ] Record log in C:\Users\amars\.kivy\logs\kivy_18-08-30_6.txt
[INFO   ] [Kivy        ] v1.10.1
[INFO   ] [Python      ] v3.7.0 (v3.7.0:1bf9cc5093, Jun 27 2018, 04:06:47) [MSC v.1914 32 bit (Intel)]
[INFO   ] [Factory     ] 194 symbols loaded
[INFO   ] [Image       ] Providers: img_tex, img_dds, img_sdl2, img_gif (img_pil, img_ffpyplayer ignored)
[INFO   ] [Text        ] Provider: sdl2
[WARNING] [Factory     ] Ignored class "ProjectScreen" re-declaration. Current -  module: None, cls: <class '__main__.ProjectScreen'>, baseclass: None, filename: None. Ignored -  module: None, cls: None, baseclass: Screen, filename: C:\Users\amars\PycharmProjects\ReportingApp\reportingapp.kv.
[INFO   ] [Window      ] Provider: sdl2
[INFO   ] [GL          ] Using the "OpenGL" graphics system
[INFO   ] [GL          ] GLEW initialization succeeded
[INFO   ] [GL          ] Backend used <glew>
[INFO   ] [GL          ] OpenGL version <b'4.3.0 - Build 20.19.15.4549'>
[INFO   ] [GL          ] OpenGL vendor <b'Intel'>
[INFO   ] [GL          ] OpenGL renderer <b'Intel(R) HD Graphics 4400'>
[INFO   ] [GL          ] OpenGL parsed version: 4, 3
[INFO   ] [GL          ] Shading version <b'4.30 - Build 20.19.15.4549'>
[INFO   ] [GL          ] Texture max size <16384>
[INFO   ] [GL          ] Texture max units <32>
[INFO   ] [Window      ] auto add sdl2 input provider
[INFO   ] [Window      ] virtual keyboard not allowed, single mode, not docked
[WARNING] [Call to deprecated function __init__ in C]\Users\amars\AppData\Local\Programs\Python\Python37-32\lib\site-packages\kivy\uix\listview.py line 845.Called from C:\Users\amars\AppData\Local\Programs\Python\Python37-32\lib\site-packages\kivy\lang\builder.py line 582 by _apply_rule().
[WARNING] [Call to deprecated function __init__ in C]\Users\amars\AppData\Local\Programs\Python\Python37-32\lib\site-packages\kivy\adapters\simplelistadapter.py line 49.Called from C:\Users\amars\AppData\Local\Programs\Python\Python37-32\lib\site-packages\kivy\uix\listview.py line 859 by __init__().
[WARNING] [Call to deprecated function __init__ in C]\Users\amars\AppData\Local\Programs\Python\Python37-32\lib\site-packages\kivy\adapters\adapter.py line 111.Called from C:\Users\amars\AppData\Local\Programs\Python\Python37-32\lib\site-packages\kivy\adapters\simplelistadapter.py line 55 by __init__().
[WARNING] [Call to deprecated function __init__ in C]\Users\amars\AppData\Local\Programs\Python\Python37-32\lib\site-packages\kivy\uix\abstractview.py line 42.Called from C:\Users\amars\AppData\Local\Programs\Python\Python37-32\lib\site-packages\kivy\uix\listview.py line 865 by __init__().
[WARNING] [Call to deprecated function __init__ in C]\Users\amars\AppData\Local\Programs\Python\Python37-32\lib\site-packages\kivy\adapters\listadapter.py line 185.Called from C:\Users\amars\PycharmProjects\ReportingApp\reportingapp.kv line 85 by <module>().
[WARNING] [Call to deprecated function __init__ in C]\Users\amars\AppData\Local\Programs\Python\Python37-32\lib\site-packages\kivy\adapters\adapter.py line 111.Called from C:\Users\amars\AppData\Local\Programs\Python\Python37-32\lib\site-packages\kivy\adapters\listadapter.py line 186 by __init__().
[INFO   ] [Base        ] Start application main loop
[INFO   ] [GL          ] NPOT texture support is available
[INFO   ] [Base        ] Leaving application in progress...
 Traceback (most recent call last):
   File "C:/Users/amars/PycharmProjects/ReportingApp/main.py", line 50, in <module>
     ReportingApp().run()
   File "C:\Users\amars\AppData\Local\Programs\Python\Python37-32\lib\site-packages\kivy\app.py", line 826, in run
     runTouchApp()
   File "C:\Users\amars\AppData\Local\Programs\Python\Python37-32\lib\site-packages\kivy\base.py", line 502, in runTouchApp
     EventLoop.window.mainloop()
   File "C:\Users\amars\AppData\Local\Programs\Python\Python37-32\lib\site-packages\kivy\core\window\window_sdl2.py", line 727, in mainloop
     self._mainloop()
   File "C:\Users\amars\AppData\Local\Programs\Python\Python37-32\lib\site-packages\kivy\core\window\window_sdl2.py", line 460, in _mainloop
     EventLoop.idle()
   File "C:\Users\amars\AppData\Local\Programs\Python\Python37-32\lib\site-packages\kivy\base.py", line 340, in idle
     self.dispatch_input()
   File "C:\Users\amars\AppData\Local\Programs\Python\Python37-32\lib\site-packages\kivy\base.py", line 325, in dispatch_input
     post_dispatch_input(*pop(0))
   File "C:\Users\amars\AppData\Local\Programs\Python\Python37-32\lib\site-packages\kivy\base.py", line 231, in post_dispatch_input
     listener.dispatch('on_motion', etype, me)
   File "kivy\_event.pyx", line 707, in kivy._event.EventDispatcher.dispatch
   File "C:\Users\amars\AppData\Local\Programs\Python\Python37-32\lib\site-packages\kivy\core\window\__init__.py", line 1360, in on_motion
     self.dispatch('on_touch_down', me)
   File "kivy\_event.pyx", line 707, in kivy._event.EventDispatcher.dispatch
   File "C:\Users\amars\AppData\Local\Programs\Python\Python37-32\lib\site-packages\kivy\core\window\__init__.py", line 1376, in on_touch_down
     if w.dispatch('on_touch_down', touch):
   File "kivy\_event.pyx", line 707, in kivy._event.EventDispatcher.dispatch
   File "C:\Users\amars\AppData\Local\Programs\Python\Python37-32\lib\site-packages\kivy\uix\screenmanager.py", line 1191, in on_touch_down
     return super(ScreenManager, self).on_touch_down(touch)
   File "C:\Users\amars\AppData\Local\Programs\Python\Python37-32\lib\site-packages\kivy\uix\widget.py", line 460, in on_touch_down
     if child.dispatch('on_touch_down', touch):
   File "kivy\_event.pyx", line 707, in kivy._event.EventDispatcher.dispatch
   File "C:\Users\amars\AppData\Local\Programs\Python\Python37-32\lib\site-packages\kivy\uix\relativelayout.py", line 288, in on_touch_down
     ret = super(RelativeLayout, self).on_touch_down(touch)
   File "C:\Users\amars\AppData\Local\Programs\Python\Python37-32\lib\site-packages\kivy\uix\widget.py", line 460, in on_touch_down
     if child.dispatch('on_touch_down', touch):
   File "kivy\_event.pyx", line 707, in kivy._event.EventDispatcher.dispatch
   File "C:\Users\amars\AppData\Local\Programs\Python\Python37-32\lib\site-packages\kivy\uix\widget.py", line 460, in on_touch_down
     if child.dispatch('on_touch_down', touch):
   File "kivy\_event.pyx", line 707, in kivy._event.EventDispatcher.dispatch
   File "C:\Users\amars\AppData\Local\Programs\Python\Python37-32\lib\site-packages\kivy\uix\widget.py", line 460, in on_touch_down
     if child.dispatch('on_touch_down', touch):
   File "kivy\_event.pyx", line 707, in kivy._event.EventDispatcher.dispatch
   File "C:\Users\amars\AppData\Local\Programs\Python\Python37-32\lib\site-packages\kivy\uix\behaviors\button.py", line 151, in on_touch_down
     self.dispatch('on_press')
   File "kivy\_event.pyx", line 703, in kivy._event.EventDispatcher.dispatch
   File "kivy\_event.pyx", line 1214, in kivy._event.EventObservers.dispatch
   File "kivy\_event.pyx", line 1098, in kivy._event.EventObservers._dispatch
   File "C:\Users\amars\AppData\Local\Programs\Python\Python37-32\lib\site-packages\kivy\lang\builder.py", line 64, in custom_callback
     exec(__kvlang__.co_value, idmap)
   File "C:\Users\amars\PycharmProjects\ReportingApp\reportingapp.kv", line 61, in <module>
     root.add_project_screen(project_name, project_address, project_scope)
   File "C:/Users/amars/PycharmProjects/ReportingApp/main.py", line 38, in add_project_screen
     s = Factory.ProjectScreen(name=name)
   File "C:\Users\amars\AppData\Local\Programs\Python\Python37-32\lib\site-packages\kivy\uix\relativelayout.py", line 265, in __init__
     super(RelativeLayout, self).__init__(**kw)
   File "C:\Users\amars\AppData\Local\Programs\Python\Python37-32\lib\site-packages\kivy\uix\floatlayout.py", line 65, in __init__
     super(FloatLayout, self).__init__(**kwargs)
   File "C:\Users\amars\AppData\Local\Programs\Python\Python37-32\lib\site-packages\kivy\uix\layout.py", line 76, in __init__
     super(Layout, self).__init__(**kwargs)
   File "C:\Users\amars\AppData\Local\Programs\Python\Python37-32\lib\site-packages\kivy\uix\widget.py", line 348, in __init__
     Builder.apply(self, ignored_consts=self._kwargs_applied_init)
   File "C:\Users\amars\AppData\Local\Programs\Python\Python37-32\lib\site-packages\kivy\lang\builder.py", line 469, in apply
     self._apply_rule(widget, rule, rule, ignored_consts=ignored_consts)
   File "C:\Users\amars\AppData\Local\Programs\Python\Python37-32\lib\site-packages\kivy\lang\builder.py", line 544, in _apply_rule
     cls = Factory_get(cname)
   File "C:\Users\amars\AppData\Local\Programs\Python\Python37-32\lib\site-packages\kivy\factory.py", line 130, in __getattr__
     raise AttributeError
 AttributeError

Process finished with exit code 1

As you can see, the app stops at line 38 in the python file, whenever I click the button in the app to call the add_project_screen method. add_project_list_item method seems to work for the listview.

Thank you.


Solution

  • Problem - AttributeError

    The cause of the AttributeError was due to typo error, button: in class rule, <ProjectScreen>:

    Solution

    Replace button: with Button:

    Recommendations

    kv file

    1. Replace dynamic class, <ProjectScreen@Screen>: with class rule, <ProjectScreen>:
    2. Instantiate a BoxLayout: widget as children of class rule, <ProjectScreen>: because there are Label and Button widgets, plus preventing widget overlapping.

    Snippet

    <ProjectScreen>:
        BoxLayout:
            orientation: 'vertical'
            Label:
                text: 'hook up project info here'
            Button:
                text: 'Back'
    

    Python code

    1. Remove import statement, from kivy.factory import Factory
    2. Remove s = Factory.ProjectScreen(name=name) from add_project_screen() method
    3. Replace self.manager.add_widget(s) with self.manager.add_widget(ProjectScreen(name=name))

    Snippet

    def add_project_screen(self,project_name_text_input, project_address_text_input, project_scope_text_input):
        name = project_name_text_input.text
        self.manager.add_widget(ProjectScreen(name=name))
    

    Example

    main.py

    from kivy.app import App    
    from kivy.uix.screenmanager import ScreenManager, Screen
    from kivy.properties import ObjectProperty
    from kivy.uix.listview import ListItemButton
    
    
    class ProjectListButton(ListItemButton):
        pass
    
    
    class HomeScreen(Screen):
        pass
    
    
    class ProjectListScreen(Screen):
        project_list = ObjectProperty(None)
    
    
    class ProjectScreen(Screen):
        pass
    
    
    class NewProjectScreen(Screen):
        project_name_text_input = ObjectProperty(None)
        project_address_text_input = ObjectProperty(None)
        project_scope_text_input = ObjectProperty(None)
    
        def add_project_list_item(self, project_name_text_input):
            new_project_name = project_name_text_input.text
            project_list_screen = self.manager.get_screen('project_list_screen')
            project_list_screen.project_list.adapter.data.extend([new_project_name])
            # Reset the ListView
            project_list_screen.project_list._trigger_reset_populate()
            #project_name_text_input.text = ''
    
        def add_project_screen(self,project_name_text_input, project_address_text_input, project_scope_text_input):
            name = project_name_text_input.text
            self.manager.add_widget(ProjectScreen(name=name))
    
    
    class ReportingApp(App):
        def build(self):
            screen_manager = ScreenManager()
            screen_manager.add_widget(HomeScreen(name="home_screen"))
            screen_manager.add_widget(ProjectListScreen(name="project_list_screen"))
            screen_manager.add_widget(NewProjectScreen(name="new_project_screen"))
            return screen_manager
    
    
    if __name__ == '__main__':
        ReportingApp().run()
    

    reporting.kv

    #:kivy 1.11.0
    
    #:import main main
    #:import ListAdapter kivy.adapters.listadapter.ListAdapter
    #:import ListItemButton kivy.uix.listview.ListItemButton
    
    
    <HomeScreen>:
        BoxLayout:
            orientation: "vertical"
            padding: 100
            spacing: 25
            Label:
                size_hint_y: None
                height: 150
                text: "Site Visit Reporting App v1.1"
    
            Button:
                text: "New Project"
                on_press:
                    root.manager.transition.direction = 'left'
                    root.manager.current = 'new_project_screen'
    
            Button:
                text: "Project List"
                on_press:
                    root.manager.transition.direction = 'left'
                    root.manager.current = 'project_list_screen'
    
    <NewProjectScreen>:
    
        project_name_text_input: project_name
        project_address_text_input: project_address
        project_scope_text_input: project_scope
    
        BoxLayout:
            orientation: "vertical"
    
            Label:
                text: 'Project Name:'
            TextInput:
                id: project_name
    
            Label:
                text: 'Project Address:'
            TextInput:
                id: project_address
    
            Label:
                text: 'Project Scope:'
            TextInput:
                id: project_scope
    
            BoxLayout:
                padding: 15
                spacing: 25
                Button:
                    text: 'OK'
                    on_press:
                        root.add_project_list_item(project_name)
                        root.add_project_screen(project_name, project_address, project_scope)
                Button:
                    text: 'Back'
                    on_press:
                        root.manager.transition.direction = 'right'
                        root.manager.current = 'home_screen'
    
    <ProjectListScreen>:
    
        project_list: project_list_view
    
        BoxLayout:
            orientation: 'vertical'
            Label:
                text: 'Current list of projects'
            Button:
                text: 'Back'
                on_press:
                    root.manager.transition.direction = 'right'
                    root.manager.current = 'home_screen'
    
            ListView:
                id: project_list_view
                adapter:
                    ListAdapter(data=[], cls=main.ProjectListButton)
    
    <ProjectScreen>:
        BoxLayout:
            orientation: 'vertical'
            Label:
                text: 'hook up project info here'
            Button:
                text: 'Back'
    

    Output

    Img01 - Project abc added Img02 - Project list