Skip to content

Modal call to configure_traits() crashes when dynamically added Traits are present. #1845

@capn-freako

Description

@capn-freako

Stack Overflow Issue

Bug Description

Using the following code (configure_traits_crash.py):

#! /usr/bin/env python

# Minimal test case for demonstrating problem w/ dynamically
# added Traits in modal call of `configure_traits()`.
#
# David Banas <capn.freako@gmail.com>
# March 4, 2025

from traits.api import HasTraits, Int

class Foo(HasTraits):
    Bar = Int(5)

    def __init__(self):
        super().__init__()
        self.add_trait("Blatz", Int(10))

if __name__ == "__main__":
    foo = Foo()
    # foo.configure_traits()            # This works.
    foo.configure_traits(kind="modal")  # This crashes.

I'm finding that the second call to configure_traits() crashes:

Traceback (most recent call last):
  File "C:\Users\dbanas\tmp\configure_traits_crash.py", line 21, in <module>
    foo.configure_traits(kind="modal")
  File "C:\Users\dbanas\.venv\pybert-dev\Lib\site-packages\traits\has_traits.py", line 2099, in configure_traits
    rc = toolkit().view_application(
         ^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "C:\Users\dbanas\.venv\pybert-dev\Lib\site-packages\traitsui\qt\toolkit.py", line 237, in view_application
    return view_application.view_application(
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "C:\Users\dbanas\.venv\pybert-dev\Lib\site-packages\traitsui\qt\view_application.py", line 92, in view_application
    return ViewApplication(
           ^^^^^^^^^^^^^^^^
  File "C:\Users\dbanas\.venv\pybert-dev\Lib\site-packages\traitsui\qt\view_application.py", line 127, in __init__
    self.ui = self.view.ui(
              ^^^^^^^^^^^^^
  File "C:\Users\dbanas\.venv\pybert-dev\Lib\site-packages\traitsui\view.py", line 457, in ui
    ui.ui(parent, kind)
  File "C:\Users\dbanas\.venv\pybert-dev\Lib\site-packages\traitsui\ui.py", line 234, in ui
    self.rebuild(self, parent)
  File "C:\Users\dbanas\.venv\pybert-dev\Lib\site-packages\traitsui\qt\toolkit.py", line 176, in ui_modal
    ui_modal.ui_modal(ui, parent)
  File "C:\Users\dbanas\.venv\pybert-dev\Lib\site-packages\traitsui\qt\ui_modal.py", line 49, in ui_modal
    _ui_dialog(ui, parent, BaseDialog.MODAL)
  File "C:\Users\dbanas\.venv\pybert-dev\Lib\site-packages\traitsui\qt\ui_modal.py", line 67, in _ui_dialog
    BaseDialog.display_ui(ui, parent, style)
  File "C:\Users\dbanas\.venv\pybert-dev\Lib\site-packages\traitsui\qt\ui_base.py", line 294, in display_ui
    ui.owner.init(ui, parent, style)
  File "C:\Users\dbanas\.venv\pybert-dev\Lib\site-packages\traitsui\qt\ui_modal.py", line 183, in init
    self.add_contents(panel(ui), bbox)
                      ^^^^^^^^^
  File "C:\Users\dbanas\.venv\pybert-dev\Lib\site-packages\traitsui\qt\ui_panel.py", line 270, in panel
    panel = _GroupPanel(content[0], ui).control
            ^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "C:\Users\dbanas\.venv\pybert-dev\Lib\site-packages\traitsui\qt\ui_panel.py", line 621, in __init__
    layout = self._add_items(content, inner)
             ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "C:\Users\dbanas\.venv\pybert-dev\Lib\site-packages\traitsui\qt\ui_panel.py", line 855, in _add_items
    editor = factory_method(
             ^^^^^^^^^^^^^^^
  File "C:\Users\dbanas\.venv\pybert-dev\Lib\site-packages\traitsui\editor_factory.py", line 117, in simple_editor
    return self.simple_editor_class(
           ^^^^^^^^^^^^^^^^^^^^^^^^^
  File "C:\Users\dbanas\.venv\pybert-dev\Lib\site-packages\traitsui\editor.py", line 540, in __init__
    self.old_value = getattr(self.object, self.name)
                     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
AttributeError: 'Foo' object has no attribute 'Blatz'

It's as if a "normal" call to configure_traits() can handle dynamically added Traits, but a modal call cannot.
Why?

My environment:

  • OS: Windows 10
$ python --version
Python 3.12.4

$ pip show Traits
Name: traits
Version: 7.0.2
{snip}

$ pip show TraitsUI
Name: traitsui
Version: 8.0.0
{snip}

Preliminary Analysis

Inspection of the two backtraces above reveals two things:

  1. the modal call is missing the Blatz trait, and

  2. the flows diverge in the call of rebuild() at traitsui/ui.py:234 and remerge 3 steps later in the call of BaseDialog.display_ui() at traitsui/qt/ui_base.py:291, only to immediately diverge again in the next step, before reunifying for good in the following step:

    (Modal)     /- ui_modal() - _ui_dialog() {qt/ui_modal.py:49} -\                /- init() {qt/ui_modal.py:73} -\
    rebuild() -                                                     - display_ui()                                  - panel() ->
    (Live)      \- ui_live()  - _ui_dialog() {qt/ui_live.py:52}  -/                \- init() {qt/ui_live.py:78}  -/
    

Inspecting the divergent blocks reveals that the first to make any changes to the passed in parameters is: init().

Aha! Adding the following debugging code to traitsui/qt/ui_modal.py:

(pybert-dev)
dbanas@Dell-XPS-15 MINGW64 ~/prj/traitsui (main)
$ git diff traitsui/qt/ui_modal.py
diff --git a/traitsui/qt/ui_modal.py b/traitsui/qt/ui_modal.py
index 7aad6c29..f7ac0e11 100644
--- a/traitsui/qt/ui_modal.py
+++ b/traitsui/qt/ui_modal.py
@@ -72,6 +72,7 @@ class _ModalDialog(BaseDialog):

     def init(self, ui, parent, style):
         """Initialise the object."""
+        print(f"traitsui.qt.ui_modal._ModalDialog.init(): `ui.context['object'].trait_names()` on entry:\n{ui.context['object'].trait_names()}")
         self.ui = ui
         self.control = ui.control
         view = ui.view
@@ -180,6 +181,7 @@ class _ModalDialog(BaseDialog):
         else:
             bbox = None

+        print(f"traitsui.qt.ui_modal._ModalDialog.init(): `ui.context['object'].trait_names()` on exit:\n{ui.context['object'].trait_names()}")
         self.add_contents(panel(ui), bbox)

     def close(self, rc=True):

yields:

(pybert-dev)
dbanas@Dell-XPS-15 MINGW64 ~/tmp
$ ./configure_traits_crash.py
traitsui.qt.ui_modal._ModalDialog.init(): `ui.context['object'].trait_names()` on entry:
['Bar', 'trait_added', 'trait_modified', 'Blatz']
traitsui.qt.ui_modal._ModalDialog.init(): `ui.context['object'].trait_names()` on exit:
['Bar', 'trait_added', 'trait_modified']

showing that the Blatz trait is getting lost somewhere in traitsui.qt.ui_modal._ModalDialog.init().

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions