From acc9bbbba483b5a01f9b171a33918fe7311d6040 Mon Sep 17 00:00:00 2001 From: Grzegorz Bokota Date: Fri, 20 Oct 2023 14:02:59 +0200 Subject: [PATCH] fix: Allow user overwritte default widget opts (#602) * fix: Allow user overwritte default widget opts * use Annotated from typing extensions --- src/magicgui/type_map/_type_map.py | 38 ++++++++++---------- src/magicgui/widgets/bases/_create_widget.py | 16 ++++----- tests/test_widgets.py | 11 ++++++ 3 files changed, 38 insertions(+), 27 deletions(-) diff --git a/src/magicgui/type_map/_type_map.py b/src/magicgui/type_map/_type_map.py index cc422846e..f15021a9e 100644 --- a/src/magicgui/type_map/_type_map.py +++ b/src/magicgui/type_map/_type_map.py @@ -214,8 +214,8 @@ def _pick_widget_type( raise_on_unknown: bool = True, ) -> WidgetTuple: """Pick the appropriate widget type for ``value`` with ``annotation``.""" - annotation, _options = _split_annotated_type(annotation) - options = {**_options, **(options or {})} + annotation, options_ = _split_annotated_type(annotation) + options = {**options_, **(options or {})} choices = options.get("choices") if is_result and annotation is inspect.Parameter.empty: @@ -229,9 +229,9 @@ def _pick_widget_type( ): return widgets.EmptyWidget, {"visible": False, **options} - _type, optional = _type_optional(value, annotation) + type_, optional = _type_optional(value, annotation) options.setdefault("nullable", optional) - choices = choices or (isinstance(_type, EnumMeta) and _type) + choices = choices or (isinstance(type_, EnumMeta) and type_) literal_choices, nullable = _literal_choices(annotation) if literal_choices is not None: choices = literal_choices @@ -243,7 +243,7 @@ def _pick_widget_type( if widget_type == "RadioButton": widget_type = "RadioButtons" warnings.warn( - f"widget_type of 'RadioButton' (with dtype {_type}) is" + f"widget_type of 'RadioButton' (with dtype {type_}) is" " being coerced to 'RadioButtons' due to choices or Enum type.", stacklevel=2, ) @@ -252,15 +252,15 @@ def _pick_widget_type( # look for subclasses for registered_type in _TYPE_DEFS: - if _type == registered_type or safe_issubclass(_type, registered_type): - _cls, opts = _TYPE_DEFS[registered_type] - return _cls, {**options, **opts} + if type_ == registered_type or safe_issubclass(type_, registered_type): + cls_, opts = _TYPE_DEFS[registered_type] + return cls_, {**options, **opts} if is_result: - _widget_type = match_return_type(_type) - if _widget_type: - _cls, opts = _widget_type - return _cls, {**options, **opts} + widget_type_ = match_return_type(type_) + if widget_type_: + cls_, opts = widget_type_ + return cls_, {**opts, **options} # Chosen for backwards/test compatibility return widgets.LineEdit, {"gui_only": True} @@ -269,14 +269,14 @@ def _pick_widget_type( wdg = widgets.Select if options.get("allow_multiple") else widgets.ComboBox return wdg, options - _widget_type = match_type(_type, value) - if _widget_type: - _cls, opts = _widget_type - return _cls, {**options, **opts} + widget_type_ = match_type(type_, value) + if widget_type_: + cls_, opts = widget_type_ + return cls_, {**opts, **options} if raise_on_unknown: raise ValueError( - f"No widget found for type {_type} and annotation {annotation!r}" + f"No widget found for type {type_} and annotation {annotation!r}" ) options["visible"] = False @@ -328,7 +328,7 @@ def get_widget_class( The WidgetClass, and dict that can be used for params. dict may be different than the options passed in. """ - widget_type, _options = _pick_widget_type( + widget_type, options_ = _pick_widget_type( value, annotation, options, is_result, raise_on_unknown ) @@ -340,7 +340,7 @@ def get_widget_class( if not safe_issubclass(widget_class, widgets.bases.Widget): assert_protocol(widget_class, WidgetProtocol) - return widget_class, _options + return widget_class, options_ def _import_wdg_class(class_name: str) -> WidgetClass: diff --git a/src/magicgui/widgets/bases/_create_widget.py b/src/magicgui/widgets/bases/_create_widget.py index 00546cbd6..fd6935eb3 100644 --- a/src/magicgui/widgets/bases/_create_widget.py +++ b/src/magicgui/widgets/bases/_create_widget.py @@ -93,7 +93,7 @@ def create_widget( assert wdg.value == "" ``` """ - _options = options.copy() if options is not None else {} + options_ = options.copy() if options is not None else {} kwargs = { "value": value, "annotation": annotation, @@ -109,21 +109,21 @@ def create_widget( from magicgui.type_map import get_widget_class if widget_type: - _options["widget_type"] = widget_type + options_["widget_type"] = widget_type # special case parameters named "password" with annotation of str if ( - not _options.get("widget_type") + not options_.get("widget_type") and (name or "").lower() == "password" and annotation is str ): - _options["widget_type"] = "Password" + options_["widget_type"] = "Password" wdg_class, opts = get_widget_class( - value, annotation, _options, is_result, raise_on_unknown + value, annotation, options_, is_result, raise_on_unknown ) if issubclass(wdg_class, Widget): - widget = wdg_class(**{**kwargs, **opts, **_options}) + widget = wdg_class(**{**kwargs, **opts, **options_}) if param_kind: widget.param_kind = param_kind # type: ignore return widget @@ -133,9 +133,9 @@ def create_widget( for p in ("Categorical", "Ranged", "Button", "Value", ""): prot = getattr(protocols, f"{p}WidgetProtocol") if isinstance(wdg_class, prot): - _options = kwargs.pop("options", None) + options_ = kwargs.pop("options", None) cls = getattr(bases, f"{p}Widget") - widget = cls(**{**kwargs, **(_options or {}), "widget_type": wdg_class}) + widget = cls(**{**kwargs, **(options_ or {}), "widget_type": wdg_class}) if param_kind: widget.param_kind = param_kind # type: ignore return widget diff --git a/tests/test_widgets.py b/tests/test_widgets.py index b12fd93a3..3d94e18fe 100644 --- a/tests/test_widgets.py +++ b/tests/test_widgets.py @@ -110,6 +110,17 @@ def test_create_widget_annotation(annotation, expected_type): wdg.close() +def test_create_widget_annotation_overwritte_parrams(): + wdg1 = widgets.create_widget(annotation=widgets.ProgressBar) + assert isinstance(wdg1, widgets.ProgressBar) + assert wdg1.visible + wdg2 = widgets.create_widget( + annotation=Annotated[widgets.ProgressBar, {"visible": False}] + ) + assert isinstance(wdg2, widgets.ProgressBar) + assert not wdg2.visible + + # fmt: off class MyBadWidget: """INCOMPLETE widget implementation and will error."""