Skip to content

Commit 4bc81e9

Browse files
hanjinliutlambert03pre-commit-ci[bot]
authored
Make all the valued containers subclass ValueWidget (pyapp-kit#663)
* use ValuedContainerWidget, refactor inheritance * typing * rename * remove backend EmptyWidget * style(pre-commit.ci): auto fixes [...] * update docs references to BaseValueWidget methods * add missing references to docs * fix references in TypeMap doc --------- Co-authored-by: Talley Lambert <[email protected]> Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
1 parent a5669e3 commit 4bc81e9

23 files changed

+520
-346
lines changed

docs/api/type_map.md

+5
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
magicgui.type_map.register_type
66
magicgui.type_map.type_registered
77
magicgui.type_map.type2callback
8+
magicgui.type_map.TypeMap
89

910
::: magicgui.type_map.get_widget_class
1011

@@ -13,3 +14,7 @@
1314
::: magicgui.type_map.type_registered
1415

1516
::: magicgui.type_map.type2callback
17+
18+
::: magicgui.type_map.TypeMap
19+
options:
20+
show_signature_annotations: false

docs/api/widgets/bases.md

+24-5
Original file line numberDiff line numberDiff line change
@@ -12,26 +12,33 @@ widgets. Therefore, it is worth being aware of the type of widget you are worki
1212
magicgui.widgets.bases.Widget
1313
magicgui.widgets.bases.ButtonWidget
1414
magicgui.widgets.bases.CategoricalWidget
15+
magicgui.widgets.bases.BaseContainerWidget
1516
magicgui.widgets.bases.ContainerWidget
17+
magicgui.widgets.bases.ValuedContainerWidget
1618
magicgui.widgets.bases.DialogWidget
1719
magicgui.widgets.bases.MainWindowWidget
1820
magicgui.widgets.bases.RangedWidget
1921
magicgui.widgets.bases.SliderWidget
2022
magicgui.widgets.bases.ValueWidget
23+
magicgui.widgets.bases.BaseValueWidget
2124

2225
## Class Hierarchy
2326

2427
In visual form, the widget class hierarchy looks like this:
2528

2629
``` mermaid
2730
classDiagram
28-
Widget <|-- ValueWidget
29-
Widget <|-- ContainerWidget
31+
Widget <|-- BaseValueWidget
32+
BaseValueWidget <|-- ValueWidget
33+
Widget <|-- BaseContainerWidget
3034
BackendWidget ..|> WidgetProtocol : implements a
3135
ValueWidget <|-- RangedWidget
3236
ValueWidget <|-- ButtonWidget
3337
ValueWidget <|-- CategoricalWidget
3438
RangedWidget <|-- SliderWidget
39+
BaseContainerWidget <|-- ContainerWidget
40+
BaseContainerWidget <|-- ValuedContainerWidget
41+
BaseValueWidget <|-- ValuedContainerWidget
3542
Widget --* WidgetProtocol : controls a
3643
<<Interface>> WidgetProtocol
3744
class WidgetProtocol {
@@ -53,12 +60,14 @@ classDiagram
5360
close()
5461
render()
5562
}
56-
class ValueWidget{
63+
class BaseValueWidget{
5764
value: Any
5865
changed: SignalInstance
5966
bind(value, call) Any
6067
unbind()
6168
}
69+
class ValueWidget{
70+
}
6271
class RangedWidget{
6372
value: float | tuple
6473
min: float
@@ -78,7 +87,7 @@ classDiagram
7887
class CategoricalWidget{
7988
choices: List[Any]
8089
}
81-
class ContainerWidget{
90+
class BaseContainerWidget{
8291
widgets: List[Widget]
8392
labels: bool
8493
layout: str
@@ -89,12 +98,13 @@ classDiagram
8998
}
9099
91100
click Widget href "#magicgui.widgets.bases.Widget"
101+
click BaseValueWidget href "#magicgui.widgets.bases.BaseValueWidget"
92102
click ValueWidget href "#magicgui.widgets.bases.ValueWidget"
93103
click RangedWidget href "#magicgui.widgets.bases.RangedWidget"
94104
click SliderWidget href "#magicgui.widgets.bases.SliderWidget"
95105
click ButtonWidget href "#magicgui.widgets.bases.ButtonWidget"
96106
click CategoricalWidget href "#magicgui.widgets.bases.CategoricalWidget"
97-
click ContainerWidget href "#magicgui.widgets.bases.ContainerWidget"
107+
click BaseContainerWidget href "#magicgui.widgets.bases.BaseContainerWidget"
98108
99109
```
100110

@@ -109,9 +119,15 @@ classDiagram
109119
::: magicgui.widgets.bases.CategoricalWidget
110120
options:
111121
heading_level: 3
122+
::: magicgui.widgets.bases.BaseContainerWidget
123+
options:
124+
heading_level: 3
112125
::: magicgui.widgets.bases.ContainerWidget
113126
options:
114127
heading_level: 3
128+
::: magicgui.widgets.bases.ValuedContainerWidget
129+
options:
130+
heading_level: 3
115131
::: magicgui.widgets.bases.DialogWidget
116132
options:
117133
heading_level: 3
@@ -127,3 +143,6 @@ classDiagram
127143
::: magicgui.widgets.bases.ValueWidget
128144
options:
129145
heading_level: 3
146+
::: magicgui.widgets.bases.BaseValueWidget
147+
options:
148+
heading_level: 3

docs/scripts/_hooks.py

+2
Original file line numberDiff line numberDiff line change
@@ -111,6 +111,8 @@ def _replace_autosummary(md: str) -> str:
111111
if name:
112112
module, _name = name.rsplit(".", 1)
113113
obj = getattr(import_module(module), _name)
114+
if obj.__doc__ is None:
115+
raise ValueError(f"Missing docstring for {obj}")
114116
table.append(f"| [`{_name}`][{name}] | {obj.__doc__.splitlines()[0]} |")
115117
lines[start:last_line] = table
116118
return "\n".join(lines)

docs/widgets.md

+1-1
Original file line numberDiff line numberDiff line change
@@ -119,7 +119,7 @@ following `ValueWidgets` track some `value`:
119119
|-----------|------|-------------|
120120
| `value` | `Any` | The current value of the widget. |
121121
| `changed` | [`psygnal.SignalInstance`][psygnal.SignalInstance] | A [`psygnal.SignalInstance`][psygnal.SignalInstance] that will emit an event when the `value` has changed. Connect callbacks to the change event using `widget.changed.connect(callback)` |
122-
| `bind` | `Any, optional` | A value or callback to bind this widget. If bound, whenever `widget.value` is accessed, the value provided here will be returned. The bound value can be a callable, in which case `bound_value(self)` will be returned (i.e. your callback must accept a single parameter, which is this widget instance.). see [`ValueWidget.bind`][magicgui.widgets.bases.ValueWidget.bind] for details. |
122+
| `bind` | `Any, optional` | A value or callback to bind this widget. If bound, whenever `widget.value` is accessed, the value provided here will be returned. The bound value can be a callable, in which case `bound_value(self)` will be returned (i.e. your callback must accept a single parameter, which is this widget instance.). see [`ValueWidget.bind`][magicgui.widgets.bases.BaseValueWidget.bind] for details. |
123123

124124
Here is a demonstration of all these:
125125

src/magicgui/backends/_ipynb/__init__.py

-2
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,6 @@
55
Container,
66
DateEdit,
77
DateTimeEdit,
8-
EmptyWidget,
98
FloatSlider,
109
FloatSpinBox,
1110
Label,
@@ -34,7 +33,6 @@
3433
"DateEdit",
3534
"TimeEdit",
3635
"DateTimeEdit",
37-
"EmptyWidget",
3836
"FloatSlider",
3937
"FloatSpinBox",
4038
"Label",

src/magicgui/backends/_ipynb/widgets.py

-13
Original file line numberDiff line numberDiff line change
@@ -143,19 +143,6 @@ def _mgui_render(self):
143143
pass
144144

145145

146-
class EmptyWidget(_IPyWidget):
147-
_ipywidget: ipywdg.Widget
148-
149-
def _mgui_get_value(self) -> Any:
150-
raise NotImplementedError()
151-
152-
def _mgui_set_value(self, value: Any) -> None:
153-
raise NotImplementedError()
154-
155-
def _mgui_bind_change_callback(self, callback: Callable):
156-
pass
157-
158-
159146
class _IPyValueWidget(_IPyWidget, protocols.ValueWidgetProtocol):
160147
def _mgui_get_value(self) -> float:
161148
return self._ipywidget.value

src/magicgui/backends/_qtpy/__init__.py

-2
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,6 @@
66
DateEdit,
77
DateTimeEdit,
88
Dialog,
9-
EmptyWidget,
109
FloatRangeSlider,
1110
FloatSlider,
1211
FloatSpinBox,
@@ -41,7 +40,6 @@
4140
"DateEdit",
4241
"DateTimeEdit",
4342
"Dialog",
44-
"EmptyWidget",
4543
"FloatRangeSlider",
4644
"FloatSlider",
4745
"FloatSpinBox",

src/magicgui/backends/_qtpy/widgets.py

-17
Original file line numberDiff line numberDiff line change
@@ -232,23 +232,6 @@ def _pre_set_hook(self, value: Any) -> Any:
232232
return value
233233

234234

235-
# BASE WIDGET
236-
237-
238-
class EmptyWidget(QBaseWidget):
239-
def __init__(self, **kwargs: Any) -> None:
240-
super().__init__(QtW.QWidget, **kwargs)
241-
242-
def _mgui_get_value(self) -> Any:
243-
raise NotImplementedError()
244-
245-
def _mgui_set_value(self, value: Any) -> None:
246-
raise NotImplementedError()
247-
248-
def _mgui_bind_change_callback(self, callback: Callable) -> None:
249-
pass
250-
251-
252235
# STRING WIDGETS
253236

254237

src/magicgui/schema/_guiclass.py

+3-3
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@
1919

2020
from magicgui.schema._ui_field import build_widget
2121
from magicgui.widgets import PushButton
22-
from magicgui.widgets.bases import ContainerWidget, ValueWidget
22+
from magicgui.widgets.bases import BaseValueWidget, ContainerWidget
2323

2424
if TYPE_CHECKING:
2525
from collections.abc import Mapping
@@ -206,7 +206,7 @@ def __set_name__(self, owner: type, name: str) -> None:
206206

207207
def __get__(
208208
self, instance: object | None, owner: type
209-
) -> ContainerWidget[ValueWidget]:
209+
) -> ContainerWidget[BaseValueWidget]:
210210
wdg = build_widget(owner if instance is None else instance)
211211

212212
# look for @button-decorated methods
@@ -317,7 +317,7 @@ def unbind_gui_from_instance(gui: ContainerWidget, instance: Any) -> None:
317317
An instance of a `guiclass`.
318318
"""
319319
for widget in gui:
320-
if isinstance(widget, ValueWidget):
320+
if isinstance(widget, BaseValueWidget):
321321
widget.changed.disconnect_setattr(instance, widget.name, missing_ok=True)
322322

323323

src/magicgui/schema/_ui_field.py

+4-4
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@
3232
from attrs import Attribute
3333
from pydantic.fields import FieldInfo, ModelField
3434

35-
from magicgui.widgets.bases import ContainerWidget, ValueWidget
35+
from magicgui.widgets.bases import BaseValueWidget, ContainerWidget
3636

3737
class HasAttrs(Protocol):
3838
"""Protocol for objects that have an ``attrs`` attribute."""
@@ -394,7 +394,7 @@ def parse_annotated(self) -> UiField[T]:
394394
kwargs.pop("name", None)
395395
return dc.replace(self, **kwargs)
396396

397-
def create_widget(self, value: T | _Undefined = Undefined) -> ValueWidget[T]:
397+
def create_widget(self, value: T | _Undefined = Undefined) -> BaseValueWidget[T]:
398398
"""Create a new Widget for this field."""
399399
from magicgui.type_map import get_widget_class
400400

@@ -786,7 +786,7 @@ def _uifields_to_container(
786786
values: Mapping[str, Any] | None = None,
787787
*,
788788
container_kwargs: Mapping | None = None,
789-
) -> ContainerWidget[ValueWidget]:
789+
) -> ContainerWidget[BaseValueWidget]:
790790
"""Create a container widget from a sequence of UiFields.
791791
792792
This function is the heart of build_widget.
@@ -849,7 +849,7 @@ def _get_values(obj: Any) -> dict | None:
849849

850850

851851
# TODO: unify this with magicgui
852-
def build_widget(cls_or_instance: Any) -> ContainerWidget[ValueWidget]:
852+
def build_widget(cls_or_instance: Any) -> ContainerWidget[BaseValueWidget]:
853853
"""Build a magicgui widget from a dataclass, attrs, pydantic, or function."""
854854
values = None if isinstance(cls_or_instance, type) else _get_values(cls_or_instance)
855855
return _uifields_to_container(get_ui_fields(cls_or_instance), values=values)

src/magicgui/type_map/_type_map.py

+5-3
Original file line numberDiff line numberDiff line change
@@ -61,7 +61,7 @@ class MissingWidget(RuntimeError):
6161
PathLike: widgets.FileEdit,
6262
}
6363

64-
_SIMPLE_TYPES_DEFAULTS = {
64+
_SIMPLE_TYPES_DEFAULTS: dict[type, type[widgets.Widget]] = {
6565
bool: widgets.CheckBox,
6666
int: widgets.SpinBox,
6767
float: widgets.FloatSpinBox,
@@ -85,6 +85,8 @@ class MissingWidget(RuntimeError):
8585

8686

8787
class TypeMap:
88+
"""Storage for mapping from types to widgets and callbacks."""
89+
8890
def __init__(
8991
self,
9092
*,
@@ -404,7 +406,7 @@ def create_widget(
404406
405407
This factory function can be used to create a widget appropriate for the
406408
provided `value` and/or `annotation` provided. See
407-
[Type Mapping Docs](../../type_map.md) for details on how the widget type is
409+
[Type Mapping Docs](../type_map.md) for details on how the widget type is
408410
determined from type annotations.
409411
410412
Parameters
@@ -453,7 +455,7 @@ def create_widget(
453455
------
454456
TypeError
455457
If the provided or autodetected `widget_type` does not implement any known
456-
[widget protocols](../protocols.md)
458+
[widget protocols](protocols.md)
457459
458460
Examples
459461
--------

0 commit comments

Comments
 (0)