Repository: kivy/kivy

Issue:
Kivy inspector module broken in 2.0.0rc4 (and 2.0.0rc3) - working fine in 1.11.1
**Software Versions**
* Python: 3.6.9
* OS: Ubuntu 18.04
* Kivy: 2.0.0rc3 and 2.0.0rc4
* Kivy installation method: pip

**Describe the bug**
The usage of the `inspector` kivy module via command line option (`-m inspector`) or via the Kivy config file (adding it to the `modules` section) crashes the app on startup with the following traceback:

```
[INFO   ] [Window      ] auto add sdl2 input provider
[WARNING] [Modules     ] Module <inspector> missing start() function
 Traceback (most recent call last):
   File "/home/andi/.pyenv/versions/kivy_env/lib/python3.6/site-packages/kivy/modules/__init__.py", line 226, in update
     self.activate_module(name, win)
   File "/home/andi/.pyenv/versions/kivy_env/lib/python3.6/site-packages/kivy/modules/__init__.py", line 181, in activate_module
     self._configure_module(name)
   File "/home/andi/.pyenv/versions/kivy_env/lib/python3.6/site-packages/kivy/modules/__init__.py", line 267, in _configure_module
     if hasattr(self.mods[name]['module'], 'configure'):
 KeyError: 'module'
[INFO   ] [Window      ] Provider: x11(['window_sdl2'] ignored)
FBConfig selected:
Doublebuffer: Yes
Red Bits: 8, Green Bits: 8, Blue Bits: 8, Alpha Bits: 8, Depth Bits: 24
[INFO   ] [GL          ] Using the "OpenGL" graphics system
[INFO   ] [GL          ] Backend used <sdl2>
[INFO   ] [GL          ] OpenGL version <b'3.0 Mesa 20.0.8'>
[INFO   ] [GL          ] OpenGL vendor <b'Intel Open Source Technology Center'>
[INFO   ] [GL          ] OpenGL renderer <b'Mesa DRI Intel(R) HD Graphics 4000 (IVB GT2)'>
[INFO   ] [GL          ] OpenGL parsed version: 3, 0
[INFO   ] [GL          ] Shading version <b'1.30'>
[INFO   ] [GL          ] Texture max size <16384>
[INFO   ] [GL          ] Texture max units <16>
[DEBUG  ] [Shader      ] Fragment compiled successfully
[DEBUG  ] [Shader      ] Vertex compiled successfully
[WARNING] [Modules     ] Module <inspector> missing start() function
 Traceback (most recent call last):
   File "/home/andi/.pyenv/versions/kivy_env/lib/python3.6/site-packages/kivy/modules/__init__.py", line 226, in update
     self.activate_module(name, win)
   File "/home/andi/.pyenv/versions/kivy_env/lib/python3.6/site-packages/kivy/modules/__init__.py", line 181, in activate_module
     self._configure_module(name)
   File "/home/andi/.pyenv/versions/kivy_env/lib/python3.6/site-packages/kivy/modules/__init__.py", line 267, in _configure_module
     if hasattr(self.mods[name]['module'], 'configure'):
 KeyError: 'module'
[CRITICAL] [Window      ] Unable to find any valuable Window provider. Please enable debug logging (e.g. add -d if running from the command line, or change the log level in the config) and re-run your app to identify potential causes
sdl2 - KeyError: 'module'
  File "/home/andi/.pyenv/versions/kivy_env/lib/python3.6/site-packages/kivy/core/__init__.py", line 70, in core_select_lib
    cls = cls()
  File "/home/andi/.pyenv/versions/kivy_env/lib/python3.6/site-packages/kivy/core/window/window_sdl2.py", line 152, in __init__
    super(WindowSDL, self).__init__()
  File "/home/andi/.pyenv/versions/kivy_env/lib/python3.6/site-packages/kivy/core/window/__init__.py", line 983, in __init__
    self.register()
  File "/home/andi/.pyenv/versions/kivy_env/lib/python3.6/site-packages/kivy/core/window/__init__.py", line 1012, in register
    Modules.register_window(self)
  File "/home/andi/.pyenv/versions/kivy_env/lib/python3.6/site-packages/kivy/modules/__init__.py", line 209, in register_window
    self.update()
  File "/home/andi/.pyenv/versions/kivy_env/lib/python3.6/site-packages/kivy/modules/__init__.py", line 226, in update
    self.activate_module(name, win)
  File "/home/andi/.pyenv/versions/kivy_env/lib/python3.6/site-packages/kivy/modules/__init__.py", line 181, in activate_module
    self._configure_module(name)
  File "/home/andi/.pyenv/versions/kivy_env/lib/python3.6/site-packages/kivy/modules/__init__.py", line 267, in _configure_module
    if hasattr(self.mods[name]['module'], 'configure'):

x11 - KeyError: 'module'
  File "/home/andi/.pyenv/versions/kivy_env/lib/python3.6/site-packages/kivy/core/__init__.py", line 70, in core_select_lib
    cls = cls()
  File "/home/andi/.pyenv/versions/kivy_env/lib/python3.6/site-packages/kivy/core/window/__init__.py", line 983, in __init__
    self.register()
  File "/home/andi/.pyenv/versions/kivy_env/lib/python3.6/site-packages/kivy/core/window/__init__.py", line 1012, in register
    Modules.register_window(self)
  File "/home/andi/.pyenv/versions/kivy_env/lib/python3.6/site-packages/kivy/modules/__init__.py", line 209, in register_window
    self.update()
  File "/home/andi/.pyenv/versions/kivy_env/lib/python3.6/site-packages/kivy/modules/__init__.py", line 226, in update
    self.activate_module(name, win)
  File "/home/andi/.pyenv/versions/kivy_env/lib/python3.6/site-packages/kivy/modules/__init__.py", line 181, in activate_module
    self._configure_module(name)
  File "/home/andi/.pyenv/versions/kivy_env/lib/python3.6/site-packages/kivy/modules/__init__.py", line 267, in _configure_module
    if hasattr(self.mods[name]['module'], 'configure'):

[DEBUG  ] [Cache       ] register <textinput.label> with limit=None, timeout=60.0
[DEBUG  ] [Cache       ] register <textinput.width> with limit=None, timeout=60.0
[INFO   ] [Kivy        ] v2.0.0rc4, git-d74461b, 20201015
[INFO   ] [Kivy        ] Installed at "/home/andi/.pyenv/versions/kivy_env/lib/python3.6/site-packages/kivy/__init__.py"
[INFO   ] [Python      ] v3.6.9 (default, Aug 28 2019, 15:54:20)
[GCC 7.4.0]
[INFO   ] [Python      ] Interpreter at "/home/andi/.pyenv/versions/kivy_env/bin/python"
[INFO   ] [SoundFFPy   ] Using ffpyplayer 4.3.2
[DEBUG  ] [Audio       ] register SoundFFPy
[DEBUG  ] [Audio       ] register SoundSDL2
[DEBUG  ] [Audio       ] register MusicSDL2
[INFO   ] [Audio       ] Providers: audio_ffpyplayer, audio_sdl2
[DEBUG  ] [App         ] Loading kv </home/andi/src/transfer_local/main.kv>
 Traceback (most recent call last):
   File "/home/andi/src/transfer_local/main.py", line 95, in <module>
     TransferLocalApp(app_name='transfer_local').run_app()
   File "/home/andi/src/ae_gui_app/ae/gui_app.py", line 992, in run_app
     self._start_event_loop()
   File "/home/andi/.pyenv/versions/kivy_env/lib/python3.6/site-packages/kivy/app.py", line 949, in run
     self._run_prepare()
   File "/home/andi/.pyenv/versions/kivy_env/lib/python3.6/site-packages/kivy/app.py", line 919, in _run_prepare
     root = self.build()
   File "/home/andi/src/ae_kivy_app/ae/kivy_app.py", line 812, in build
     Window.bind(on_resize=self.win_pos_size_change,
 AttributeError: 'NoneType' object has no attribute 'bind'
```

When I explicitly import the inspector module and call the create_inspector() function in the build() method of the App class then the app at least starts with no errors, but it crashes on closing the app with activated inspector with the following traceback:

```
 Traceback (most recent call last):
   File "/home/andi/src/transfer_local/main.py", line 95, in <module>
     TransferLocalApp(app_name='transfer_local').run_app()
   File "/home/andi/src/ae_gui_app/ae/gui_app.py", line 992, in run_app
     self._start_event_loop()
   File "/home/andi/.pyenv/versions/kivy_env/lib/python3.6/site-packages/kivy/app.py", line 951, in run
     self.stop()
   File "/home/andi/.pyenv/versions/kivy_env/lib/python3.6/site-packages/kivy/app.py", line 977, in stop
     self._app_window.remove_widget(child)
   File "/home/andi/.pyenv/versions/kivy_env/lib/python3.6/site-packages/kivy/core/window/__init__.py", line 1329, in remove_widget
     self.children.remove(widget)
   File "kivy/properties.pyx", line 756, in kivy.properties.ObservableList.remove
   File "kivy/properties.pyx", line 707, in kivy.properties.observable_list_dispatch
   File "kivy/properties.pyx", line 600, in kivy.properties.Property.dispatch
   File "kivy/_event.pyx", line 1248, in kivy._event.EventObservers.dispatch
   File "kivy/_event.pyx", line 1154, in kivy._event.EventObservers._dispatch
   File "/home/andi/.pyenv/versions/kivy_env/lib/python3.6/site-packages/kivy/modules/inspector.py", line 383, in on_window_children
     win.add_widget(self)
   File "/home/andi/.pyenv/versions/kivy_env/lib/python3.6/site-packages/kivy/core/window/__init__.py", line 1307, in add_widget
     (widget, widget.parent)
 kivy.uix.widget.WidgetException: Cannot add <kivy.modules.inspector.Inspector object at 0x7fdc5143d5f8> to window, it already has a parent <kivy.core.window.window_sdl2.WindowSDL object at 0x7fdc5aab7908>
```

This is *not* happening if I use the last stable Kivy version (1.11.1).


**Expected behavior**
All inspector module activation methods should work as documented.

**To Reproduce**
see above in error description (*Describe the bug*).



Discussion:
I don't know how to fix this, but I found a workaround:

```python
from kivy.app import App
from kivy.uix.widget import Widget


def activate_inspector():
    from kivy.config import Config
    from kivy.modules import Modules
    from kivy.core.window import Window
    Config.set('modules', 'inspector', '')
    Modules.activate_module('inspector', Window)


def deactivate_inspector():
    from kivy.modules import Modules
    from kivy.core.window import Window
    Modules.deactivate_module('inspector', Window)


class SampleApp(App):
    def on_start(self):
        activate_inspector()
    def on_stop(self):
        deactivate_inspector()
    def build(self):
        return Widget()


if __name__ == '__main__':
    SampleApp().run()
```
You can do this until someone fixes the issue.
You can do it more directly with `inspector.create_inspector(Window, root)` and `inspector.stop(Window, self.root)` to start and stop the inspector without going through modules.

Resolving patch:
diff --git a/kivy/factory_registers.py b/kivy/factory_registers.py
--- a/kivy/factory_registers.py
+++ b/kivy/factory_registers.py
@@ -46,6 +46,7 @@
 r('Rotate', module='kivy.graphics.context_instructions')
 r('Scale', module='kivy.graphics.context_instructions')
 r('Translate', module='kivy.graphics.context_instructions')
+r('Transform', module='kivy.graphics.context_instructions')
 r('MatrixInstruction', module='kivy.graphics.context_instructions')
 r('Fbo', module='kivy.graphics.fbo')
 r('Instruction', module='kivy.graphics.instructions')
diff --git a/kivy/modules/inspector.py b/kivy/modules/inspector.py
--- a/kivy/modules/inspector.py
+++ b/kivy/modules/inspector.py
@@ -54,36 +54,22 @@ def build(self):
 
 __all__ = ('start', 'stop', 'create_inspector')
 
-import kivy
-kivy.require('1.0.9')
-
 import weakref
 from functools import partial
 from itertools import chain
+
 from kivy.animation import Animation
 from kivy.logger import Logger
-from kivy.uix.widget import Widget
-from kivy.uix.button import Button
-from kivy.uix.label import Label
-from kivy.uix.togglebutton import ToggleButton
-from kivy.uix.textinput import TextInput
-from kivy.uix.image import Image
-from kivy.uix.treeview import TreeViewNode, TreeView
-from kivy.uix.gridlayout import GridLayout
-from kivy.uix.floatlayout import FloatLayout
-from kivy.uix.boxlayout import BoxLayout
-from kivy.uix.modalview import ModalView
-from kivy.graphics import Color, Rectangle, PushMatrix, PopMatrix
-from kivy.graphics.context_instructions import Transform
 from kivy.graphics.transformation import Matrix
-from kivy.properties import ObjectProperty, BooleanProperty, ListProperty, \
-    NumericProperty, StringProperty, OptionProperty, \
-    ReferenceListProperty, AliasProperty, VariableListProperty
-from kivy.graphics.texture import Texture
 from kivy.clock import Clock
 from kivy.lang import Builder
+from kivy.factory import Factory
 from kivy.weakproxy import WeakProxy
-
+from kivy.properties import (
+    ObjectProperty, BooleanProperty, ListProperty,
+    NumericProperty, StringProperty, OptionProperty,
+    ReferenceListProperty, AliasProperty, VariableListProperty
+)
 
 Builder.load_string('''
 <Inspector>:
@@ -237,7 +223,7 @@ def build(self):
 ''')
 
 
-class TreeViewProperty(BoxLayout, TreeViewNode):
+class TreeViewProperty(Factory.BoxLayout, Factory.TreeViewNode):
 
     widget_ref = ObjectProperty(None, allownone=True)
 
@@ -259,11 +245,11 @@ def _get_widget(self):
     refresh = BooleanProperty(False)
 
 
-class TreeViewWidget(Label, TreeViewNode):
+class TreeViewWidget(Factory.Label, Factory.TreeViewNode):
     widget = ObjectProperty(None)
 
 
-class WidgetTree(TreeView):
+class WidgetTree(Factory.TreeView):
     selected_widget = ObjectProperty(None, allownone=True)
 
     __events__ = ('on_select_widget',)
@@ -317,7 +303,7 @@ def _update_scroll(self, *args):
         self.parent.scroll_to(node)
 
 
-class Inspector(FloatLayout):
+class Inspector(Factory.FloatLayout):
 
     widget = ObjectProperty(None, allownone=True)
 
@@ -344,11 +330,11 @@ def __init__(self, **kwargs):
         super(Inspector, self).__init__(**kwargs)
         self.avoid_bring_to_top = False
         with self.canvas.before:
-            self.gcolor = Color(1, 0, 0, .25)
-            PushMatrix()
-            self.gtransform = Transform(Matrix())
-            self.grect = Rectangle(size=(0, 0))
-            PopMatrix()
+            self.gcolor = Factory.Color(1, 0, 0, .25)
+            Factory.PushMatrix()
+            self.gtransform = Factory.Transform(Matrix())
+            self.grect = Factory.Rectangle(size=(0, 0))
+            Factory.PopMatrix()
         Clock.schedule_interval(self.update_widget_graphics, 0)
 
     def on_touch_down(self, touch):
@@ -389,8 +375,11 @@ def highlight_at(self, x, y):
         # modalviews before others
         win_children = self.win.children
         children = chain(
-            (c for c in win_children if isinstance(c, ModalView)),
-            (c for c in reversed(win_children) if not isinstance(c, ModalView))
+            (c for c in win_children if isinstance(c, Factory.ModalView)),
+            (
+                c for c in reversed(win_children)
+                if not isinstance(c, Factory.ModalView)
+            )
         )
         for child in children:
             if child is self:
@@ -408,7 +397,7 @@ def highlight_widget(self, widget, info=True, *largs):
         if self.widget_info and info:
             self.show_widget_info()
 
-    def update_widget_graphics(self, *l):
+    def update_widget_graphics(self, *largs):
         if not self.activated:
             return
         if self.widget is None:
@@ -545,7 +534,7 @@ def show_widget_info(self):
                 pass
             treeview.add_node(node)
 
-    def update_node_content(self, node, *l):
+    def update_node_content(self, node, *largs):
         node = node()
         if node is None:
             return
@@ -568,7 +557,7 @@ def keyboard_shortcut(self, win, scancode, *largs):
                 self.activated = False
                 return True
 
-    def show_property(self, instance, value, key=None, index=-1, *l):
+    def show_property(self, instance, value, key=None, index=-1, *largs):
         # normal call: (tree node, focus, )
         # nested call: (widget, prop value, prop key, index in dict/list)
         if value is False:
@@ -600,22 +589,26 @@ def show_property(self, instance, value, key=None, index=-1, *l):
                 dtype = 'list'
 
         if isinstance(prop, NumericProperty) or dtype == 'numeric':
-            content = TextInput(text=str(value) or '', multiline=False)
+            content = Factory.TextInput(text=str(value) or '', multiline=False)
             content.bind(text=partial(
                 self.save_property_numeric, widget, key, index))
         elif isinstance(prop, StringProperty) or dtype == 'string':
-            content = TextInput(text=value or '', multiline=True)
+            content = Factory.TextInput(text=value or '', multiline=True)
             content.bind(text=partial(
                 self.save_property_text, widget, key, index))
         elif (isinstance(prop, ListProperty) or
               isinstance(prop, ReferenceListProperty) or
               isinstance(prop, VariableListProperty) or
               dtype == 'list'):
-            content = GridLayout(cols=1, size_hint_y=None)
+            content = Factory.GridLayout(cols=1, size_hint_y=None)
             content.bind(minimum_height=content.setter('height'))
             for i, item in enumerate(value):
-                button = Button(text=repr(item), size_hint_y=None, height=44)
-                if isinstance(item, Widget):
+                button = Factory.Button(
+                    text=repr(item),
+                    size_hint_y=None,
+                    height=44
+                )
+                if isinstance(item, Factory.Widget):
                     button.bind(on_release=partial(self.highlight_widget, item,
                                                    False))
                 else:
@@ -623,10 +616,10 @@ def show_property(self, instance, value, key=None, index=-1, *l):
                                                    item, key, i))
                 content.add_widget(button)
         elif isinstance(prop, OptionProperty):
-            content = GridLayout(cols=1, size_hint_y=None)
+            content = Factory.GridLayout(cols=1, size_hint_y=None)
             content.bind(minimum_height=content.setter('height'))
             for option in prop.options:
-                button = ToggleButton(
+                button = Factory.ToggleButton(
                     text=option,
                     state='down' if option == value else 'normal',
                     group=repr(content.uid), size_hint_y=None,
@@ -635,17 +628,17 @@ def show_property(self, instance, value, key=None, index=-1, *l):
                     self.save_property_option, widget, key))
                 content.add_widget(button)
         elif isinstance(prop, ObjectProperty):
-            if isinstance(value, Widget):
-                content = Button(text=repr(value))
+            if isinstance(value, Factory.Widget):
+                content = Factory.Button(text=repr(value))
                 content.bind(on_release=partial(self.highlight_widget, value))
-            elif isinstance(value, Texture):
-                content = Image(texture=value)
+            elif isinstance(value, Factory.Texture):
+                content = Factory.Image(texture=value)
             else:
-                content = Label(text=repr(value))
+                content = Factory.Label(text=repr(value))
 
         elif isinstance(prop, BooleanProperty):
             state = 'down' if value else 'normal'
-            content = ToggleButton(text=key, state=state)
+            content = Factory.ToggleButton(text=key, state=state)
             content.bind(on_release=partial(self.save_property_boolean, widget,
                                             key, index))
 
@@ -681,7 +674,7 @@ def save_property_boolean(self, widget, key, index, instance, ):
         except:
             pass
 
-    def save_property_option(self, widget, key, instance, *l):
+    def save_property_option(self, widget, key, instance, *largs):
         try:
             setattr(widget, key, instance.text)
         except:
@@ -726,7 +719,7 @@ def update_widget_tree(self, *args):
         self.widgettree.update_selected_widget(self.widget)
 
 
-def create_inspector(win, ctx, *l):
+def create_inspector(win, ctx, *largs):
     '''Create an Inspector instance attached to the *ctx* and bound to the
     Window's :meth:`~kivy.core.window.WindowBase.on_keyboard` event for
     capturing the keyboard shortcut.


Test patch:
diff --git a/kivy/tests/test_module_inspector.py b/kivy/tests/test_module_inspector.py
--- a/kivy/tests/test_module_inspector.py
+++ b/kivy/tests/test_module_inspector.py
@@ -226,7 +226,6 @@ def test_widget_popup(self, *args):
 
         # checked widget
         popup = self.root.ids.popup
-        popup_exp = self.root.ids.popup.text
 
         # activate inspector with root as ctx
         inspector.start(self._win, self.root)
@@ -310,7 +309,6 @@ def test_widget_multipopup(self, *args):
 
         # checked widget
         popup = self.root.ids.popup
-        popup_exp = self.root.ids.popup.text
 
         # activate inspector with root as ctx
         inspector.start(self._win, self.root)