Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 6 additions & 2 deletions src/magicgui/widgets/bases/_container_widget.py
Original file line number Diff line number Diff line change
Expand Up @@ -341,8 +341,12 @@ def remove(self, value: Widget | str) -> None:
super().remove(value) # type: ignore

def __delattr__(self, name: str) -> None:
"""Delete a widget by name."""
self.remove(name)
"""Delete a widget by name, or a plain attribute if no widget matches."""
for widget in self._list:
if name == widget.name:
self.remove(widget)
return
object.__delattr__(self, name)

def __delitem__(self, key: int | slice) -> None:
"""Delete a widget by integer or slice index."""
Expand Down
18 changes: 18 additions & 0 deletions tests/test_container.py
Original file line number Diff line number Diff line change
Expand Up @@ -117,6 +117,24 @@ def test_delete_widget():
container.index(a)


def test_delete_non_widget_attribute():
"""Deleting a plain attribute does not go through widget removal."""
a = widgets.Label(name="a")
container = widgets.Container(widgets=[a])
container.x = 5
del container.x
assert not hasattr(container, "x")

# a widget that was already removed can still be dereferenced
container.remove(a)
container.a_ref = a
del container.a_ref

# a name that is neither a widget nor an attribute still raises AttributeError
with pytest.raises(AttributeError):
del container.nonexistent


def test_reset_choice_recursion():
"""Test that reset_choices recursion works for multiple types of widgets."""
x = 0
Expand Down
Loading