Skip to content

Commit

Permalink
fix: fix binding of non ValueWidgets in guiclass (#556)
Browse files Browse the repository at this point in the history
* fix: fix binding of non ValueWidgets in guiclass

* test: only test ipywidgets on 3.9+

* test: skip ipywidgets when missing

* pin ipython differently

* revert test

* test: update

* test: use stem

* add magic class testing extra

* remove other deps
  • Loading branch information
tlambert03 committed May 11, 2023
1 parent a3d091b commit 32ca80d
Show file tree
Hide file tree
Showing 4 changed files with 39 additions and 12 deletions.
3 changes: 1 addition & 2 deletions .github/workflows/test_and_deploy.yml
Original file line number Diff line number Diff line change
Expand Up @@ -101,8 +101,7 @@ jobs:
run: |
python -m pip install --upgrade pip
python -m pip install -e .[testing,pyqt5]
pip install numpy superqt vispy pyqtgraph
python -m pip install ./magic-class
python -m pip install ./magic-class[testing]
- name: Test magicclass
uses: aganders3/headless-gui@v1
Expand Down
3 changes: 1 addition & 2 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,6 @@ testing = [
"matplotlib",
"toolz",
"ipywidgets",
"ipython<8.13; python_version<'3.9'",
"ipykernel",
"pydantic",
"attrs",
Expand Down Expand Up @@ -122,7 +121,7 @@ docs = [
"pint",
"ipywidgets>=8.0.0",
"ipykernel",
"pyside6==6.4.2", # 6.4.3 gives segfault for some reason
"pyside6==6.4.2", # 6.4.3 gives segfault for some reason
]

[project.urls]
Expand Down
24 changes: 16 additions & 8 deletions src/magicgui/schema/_guiclass.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@
from dataclasses import Field, dataclass, field, is_dataclass
from typing import TYPE_CHECKING, Any, Callable, ClassVar, TypeVar, overload

from psygnal import SignalGroup, evented
from psygnal import SignalGroup, SignalInstance, evented
from psygnal import __version__ as psygnal_version

from magicgui.schema._ui_field import build_widget
Expand Down Expand Up @@ -264,27 +264,35 @@ def bind_gui_to_instance(
warnings.simplefilter("ignore", category=RuntimeWarning)

for widget in gui:
# would be cleaner to check isinstance(widget, ValueWidget)... but a few
# widgets (like FileEdit) are not ValueWidgets, but still have a `changed`
# signal and a value method. So for now we just check for the protocol.
changed = getattr(widget, "changed", None)

# we skip PushButton here, otherwise we will set the value of the
# button (a boolean) to some attribute on the instance, which is probably
# not what we want.
if isinstance(widget, ValueWidget) and not isinstance(widget, PushButton):
if isinstance(changed, SignalInstance) and not isinstance(
widget, PushButton
):
name: str = getattr(widget, "name", "")
# connect changes in the widget to the instance
if hasattr(instance, widget.name):
if hasattr(instance, name):
try:
widget.changed.connect_setattr(
instance, widget.name, **_IGNORE_REF_ERR # type: ignore
changed.connect_setattr(
instance, name, **_IGNORE_REF_ERR # type: ignore
)
except TypeError:
warnings.warn(
f"Could not bind {widget.name} to {instance}. "
f"Could not bind {name} to {instance}. "
"This may be because the instance has __slots__ or "
"other attribute restrictions. Please update psygnal.",
stacklevel=2,
)

# connect changes from the instance to the widget
if widget.name in signals:
signals[widget.name].connect_setattr(widget, "value")
if name in signals:
signals[name].connect_setattr(widget, "value")


def unbind_gui_from_instance(gui: ContainerWidget, instance: Any) -> None:
Expand Down
21 changes: 21 additions & 0 deletions tests/test_gui_class.py
Original file line number Diff line number Diff line change
Expand Up @@ -146,3 +146,24 @@ def foo(self):
assert t2.gui.y.value == "baz"
assert isinstance(t2.gui.foo, PushButton)
assert t2.foo() == {"x": 3, "y": "baz"}


def test_path_update():
"""One off test for FileEdits... which weren't updating.
(The deeper issue is that things like FileEdit don't subclass ValueWidget...)
"""

from pathlib import Path

@guiclass
class MyGuiClass:
a: Path = Path("blabla")

obj = MyGuiClass()
assert obj.gui.a.value.stem == "blabla"
assert obj.a.stem == "blabla"

obj.gui.a.value = "foo"
assert obj.gui.a.value.stem == "foo"
assert obj.a.stem == "foo"

0 comments on commit 32ca80d

Please sign in to comment.